From afed5f3afc62431735b48a0ca6b5fb16ec598cf3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 27 Jan 2016 11:30:12 +0100 Subject: [PATCH 001/273] Test code for the DFRN import --- include/import-dfrn.php | 376 ++++++++++++++++++++++++++++++++++++++++ include/items.php | 9 + 2 files changed, 385 insertions(+) create mode 100644 include/import-dfrn.php diff --git a/include/import-dfrn.php b/include/import-dfrn.php new file mode 100644 index 0000000000..7336946a7b --- /dev/null +++ b/include/import-dfrn.php @@ -0,0 +1,376 @@ +<?php +/* +require_once("include/Contact.php"); +require_once("include/threads.php"); +require_once("include/html2bbcode.php"); +require_once("include/bbcode.php"); +require_once("include/items.php"); +require_once("mod/share.php"); +require_once("include/enotify.php"); +require_once("include/socgraph.php"); +require_once("include/Photo.php"); +require_once("include/Scrape.php"); +require_once("include/follow.php"); +require_once("include/api.php"); +require_once("mod/proxy.php"); +*/ + +define("NS_ATOM", "http://www.w3.org/2005/Atom"); +define("NS_THR", "http://purl.org/syndication/thread/1.0"); +define("NS_GEORSS", "http://www.georss.org/georss"); +define("NS_ACTIVITY", "http://activitystrea.ms/spec/1.0/"); +define("NS_MEDIA", "http://purl.org/syndication/atommedia"); +define("NS_POCO", "http://portablecontacts.net/spec/1.0"); +define("NS_OSTATUS", "http://ostatus.org/schema/1.0"); +define("NS_STATUSNET", "http://status.net/schema/api/1/"); + +class dfrn2 { + function fetchauthor($xpath, $context, $importer, $element, &$contact, $onlyfetch) { + + $author = array(); + $author["name"] = $xpath->evaluate($element.'/atom:name/text()', $context)->item(0)->nodeValue; + $author["link"] = $xpath->evaluate($element.'/atom:uri/text()', $context)->item(0)->nodeValue; + + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", + intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), + dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); + if ($r) { + $contact = $r[0]; + $author["contact-id"] = $r[0]["id"]; + $author["network"] = $r[0]["network"]; + } else { + $author["contact-id"] = $contact["id"]; + $author["network"] = $contact["network"]; + } + + // Until now we aren't serving different sizes - but maybe later + $avatarlist = array(); + // @todo check if "avatar" or "photo" would be the best field in the specification + $avatars = $xpath->query($element."/atom:link[@rel='avatar']", $context); + foreach($avatars AS $avatar) { + $href = ""; + $width = 0; + foreach($avatar->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "width") + $width = $attributes->textContent; + } + if (($width > 0) AND ($href != "")) + $avatarlist[$width] = $href; + } + if (count($avatarlist) > 0) { + krsort($avatarlist); + $author["avatar"] = current($avatarlist); + } + + if ($r AND !$onlyfetch) { + // Update contact data + + $value = $xpath->evaluate($element.'/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; + + $value = $xpath->evaluate($element.'/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; + + $value = $xpath->evaluate($element.'/poco:note/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["about"] = $value; + + $value = $xpath->evaluate($element.'/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; + + /// @todo + /// poco:birthday + /// poco:utcOffset + /// poco:updated + /// poco:ims + /// poco:tags + +/* + if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { + + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); + + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d AND `network` = '%s'", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), + dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS)); + + } + + if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['photo'])) { + logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); + + $photos = import_profile_photo($author["author-avatar"], $importer["uid"], $contact["id"]); + + q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d AND `network` = '%s'", + dbesc($author["author-avatar"]), dbesc($photos[1]), dbesc($photos[2]), + dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS)); + } +*/ + /// @todo Add the "addr" field +// $contact["generation"] = 2; +// $contact["photo"] = $author["avatar"]; +//print_r($contact); + //update_gcontact($contact); + } + + return($author); + } + + function import($xml,$importer,&$contact, &$hub) { + + $a = get_app(); + + logger("Import DFRN message", LOGGER_DEBUG); + + if ($xml == "") + return; + + $doc = new DOMDocument(); + @$doc->loadXML($xml); + + $xpath = new DomXPath($doc); + $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom"); + $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0"); + $xpath->registerNamespace('at', "http://purl.org/atompub/tombstones/1.0"); + $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia"); + $xpath->registerNamespace('dfrn', "http://purl.org/macgirvin/dfrn/1.0"); + $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/"); + $xpath->registerNamespace('georss', "http://www.georss.org/georss"); + $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0"); + $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0"); + $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/"); + + $header = array(); + $header["uid"] = $importer["uid"]; + $header["network"] = NETWORK_DFRN; + $header["type"] = "remote"; + $header["wall"] = 0; + $header["origin"] = 0; + $header["gravity"] = GRAVITY_PARENT; + $header["contact-id"] = $importer["id"]; + + // Update the contact table if the data has changed + // Only the "dfrn:owner" in the head section contains all data + self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", $contact, false); + + $entries = $xpath->query('/atom:feed/atom:entry'); + + $item_id = 0; + + // Reverse the order of the entries + $entrylist = array(); + + foreach ($entries AS $entry) + $entrylist[] = $entry; + + foreach (array_reverse($entrylist) AS $entry) { + + $item = $header; + + $mention = false; + + // Fetch the owner + $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", $contact, true); + + $item["owner-name"] = $owner["name"]; + $item["owner-link"] = $owner["link"]; + $item["owner-avatar"] = $owner["avatar"]; + + if ($header["contact-id"] != $owner["contact-id"]) + $item["contact-id"] = $owner["contact-id"]; + + if (($header["network"] != $owner["network"]) AND ($owner["network"] != "")) + $item["network"] = $owner["network"]; + + // fetch the author + $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", $contact, true); + + $item["author-name"] = $author["name"]; + $item["author-link"] = $author["link"]; + $item["author-avatar"] = $author["avatar"]; + + if ($header["contact-id"] != $author["contact-id"]) + $item["contact-id"] = $author["contact-id"]; + + if (($header["network"] != $author["network"]) AND ($author["network"] != "")) + $item["network"] = $author["network"]; + + // Now get the item + $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["uri"])); + if ($r) { + //logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); + //continue; + } + + // Is it a reply? + $inreplyto = $xpath->query('thr:in-reply-to', $entry); + if (is_object($inreplyto->item(0))) { + $objecttype = ACTIVITY_OBJ_COMMENT; + $item["type"] = 'remote-comment'; + $item["gravity"] = GRAVITY_COMMENT; + + foreach($inreplyto->item(0)->attributes AS $attributes) { + if ($attributes->name == "ref") + $item["parent-uri"] = $attributes->textContent; + } + } else { + $objecttype = ACTIVITY_OBJ_NOTE; + $item["parent-uri"] = $item["uri"]; + } + + $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + + $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; + $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; + + $item["body"] = $xpath->query('dfrn:env/text()', $entry)->item(0)->nodeValue; + $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]); + // make sure nobody is trying to sneak some html tags by us + $item["body"] = notags(base64url_decode($item["body"])); + + $item["body"] = limit_body_size($item["body"]); + + /// @todo Do we need the old check for HTML elements? + + // We don't need the content element since "dfrn:env" is always present + //$item["body"] = $xpath->query('atom:content/text()', $entry)->item(0)->nodeValue; + + $item["last-child"] = $xpath->query('dfrn:comment-allow/text()', $entry)->item(0)->nodeValue; + $item["location"] = $xpath->query('dfrn:location/text()', $entry)->item(0)->nodeValue; + + $georsspoint = $xpath->query('georss:point', $entry); + if ($georsspoint) + $item["coord"] = $georsspoint->item(0)->nodeValue; + + $item["private"] = $xpath->query('dfrn:private/text()', $entry)->item(0)->nodeValue; + + $item["extid"] = $xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue; + + if ($xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue == "true") + $item["bookmark"] = true; + + $notice_info = $xpath->query('statusnet:notice_info', $entry); + if ($notice_info AND ($notice_info->length > 0)) { + foreach($notice_info->item(0)->attributes AS $attributes) { + if ($attributes->name == "source") + $item["app"] = strip_tags($attributes->textContent); + } + } + + $item["guid"] = $xpath->query('dfrn:diaspora_guid/text()', $entry)->item(0)->nodeValue; + + // dfrn:diaspora_signature + + $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; + + if ($xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue != "") + $objecttype = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; + + $item["object-type"] = $objecttype; + + // activity:object + + // activity:target + + $categories = $xpath->query('atom:category', $entry); + if ($categories) { + foreach ($categories AS $category) { + foreach($category->attributes AS $attributes) + if ($attributes->name == "term") { + $term = $attributes->textContent; + if(strlen($item["tag"])) + $item["tag"] .= ','; + $item["tag"] .= "#[url=".$a->get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + } + } + } + + $enclosure = ""; + + $links = $xpath->query('atom:link', $entry); + if ($links) { + $rel = ""; + $href = ""; + $type = ""; + $length = "0"; + $title = ""; + foreach ($links AS $link) { + foreach($link->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "rel") + $rel = $attributes->textContent; + if ($attributes->name == "type") + $type = $attributes->textContent; + if ($attributes->name == "length") + $length = $attributes->textContent; + if ($attributes->name == "title") + $title = $attributes->textContent; + } + if (($rel != "") AND ($href != "")) + switch($rel) { + case "alternate": + $item["plink"] = $href; + break; + case "enclosure": + $enclosure = $href; + if(strlen($item["attach"])) + $item["attach"] .= ','; + + $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; + break; + case "mentioned": + // Notification check + if ($importer["nurl"] == normalise_link($href)) + $mention = true; + break; + } + } + } + + print_r($item); +/* + if (!$item_id) { + logger("Error storing item", LOGGER_DEBUG); + continue; + } + + logger("Item was stored with id ".$item_id, LOGGER_DEBUG); + $item["id"] = $item_id; +*/ + +/* + if ($mention) { + $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($item['uid'])); + $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($item_id)); + + notification(array( + 'type' => NOTIFY_TAGSELF, + 'notify_flags' => $u[0]["notify-flags"], + 'language' => $u[0]["language"], + 'to_name' => $u[0]["username"], + 'to_email' => $u[0]["email"], + 'uid' => $item["uid"], + 'item' => $item, + 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($item_id)), + 'source_name' => $item["author-name"], + 'source_link' => $item["author-link"], + 'source_photo' => $item["author-avatar"], + 'verb' => ACTIVITY_TAG, + 'otype' => 'item', + 'parent' => $r[0]["parent"] + )); + } +*/ + } + } +} +?> diff --git a/include/items.php b/include/items.php index cf044d8837..f52d46b7e2 100644 --- a/include/items.php +++ b/include/items.php @@ -1766,6 +1766,12 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) return; } + // Test - remove before flight + //if ($pass < 2) { + // $tempfile = tempnam(get_temppath(), "dfrn-consume-"); + // file_put_contents($tempfile, $xml); + //} + require_once('library/simplepie/simplepie.inc'); require_once('include/contact_selectors.php'); @@ -2471,6 +2477,9 @@ function local_delivery($importer,$data) { logger(__function__, LOGGER_TRACE); + //$tempfile = tempnam(get_temppath(), "dfrn-local-"); + //file_put_contents($tempfile, $data); + if($importer['readonly']) { // We aren't receiving stuff from this person. But we will quietly ignore them // rather than a blatant "go away" message. From 1d4de969603906646de7a0015346f6fdadd39a89 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 27 Jan 2016 15:57:11 +0100 Subject: [PATCH 002/273] Next steps to add all fields --- include/dfrn.php | 12 +++- include/import-dfrn.php | 125 +++++++++++++++++++++++++++++++++++----- 2 files changed, 121 insertions(+), 16 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 2c8e8ce38f..883afe15f7 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -396,7 +396,6 @@ class dfrn { $root->setAttribute("xmlns:ostatus", NS_OSTATUS); $root->setAttribute("xmlns:statusnet", NS_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"]); @@ -409,9 +408,11 @@ class dfrn { $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink); xml_add_element($doc, $root, "link", "", $attributes); - ostatus_hublinks($doc, $root); if ($public) { + // 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"]); xml_add_element($doc, $root, "link", "", $attributes); @@ -425,6 +426,8 @@ class dfrn { if ($owner['page-flags'] == PAGE_COMMUNITY) xml_add_element($doc, $root, "dfrn:community", 1); + /// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP" + xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); $author = self::add_author($doc, $owner, $authorelement, $public); @@ -727,9 +730,14 @@ class dfrn { xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); + // "dfrn:env" is used to read the content xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true)); + + // The "content" field is not read by the receiver. We could remove it when the type is "text" + // We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env" xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type)); + // 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"])); diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 7336946a7b..5a35829263 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -25,7 +25,48 @@ define("NS_OSTATUS", "http://ostatus.org/schema/1.0"); define("NS_STATUSNET", "http://status.net/schema/api/1/"); class dfrn2 { - function fetchauthor($xpath, $context, $importer, $element, &$contact, $onlyfetch) { + /** + * @brief Add new birthday event for this person + * + * @param array $contact Contact record + * @param string $birthday Birthday of the contact + * + */ + private function birthday_event($contact, $birthday) { + + logger('updating birthday: '.$birthday.' for contact '.$contact['id']); + + $bdtext = sprintf(t('%s\'s birthday'), $contact['name']); + $bdtext2 = sprintf(t('Happy Birthday %s'), ' [url=' . $contact['url'].']'.$contact['name'].'[/url]' ) ; + + + $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + intval($contact['uid']), + intval($contact['id']), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert('UTC','UTC', $birthday)), + dbesc(datetime_convert('UTC','UTC', $birthday.' + 1 day ')), + dbesc($bdtext), + dbesc($bdtext2), + dbesc('birthday') + ); + } + + /** + * @brief Fetch the author data from head or entry items + * + * @param object $xpath XPath object + * @param object $context In which context should the data be searched + * @param array $importer Record of the importer contact + * @param string $element Element name from which the data is fetched + * @param array $contact The updated contact record of the author + * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well + * + * @return Returns an array with relevant data of the author + */ + private function fetchauthor($xpath, $context, $importer, $element, &$contact, $onlyfetch) { $author = array(); $author["name"] = $xpath->evaluate($element.'/atom:name/text()', $context)->item(0)->nodeValue; @@ -55,6 +96,8 @@ class dfrn2 { $href = $attributes->textContent; if ($attributes->name == "width") $width = $attributes->textContent; + if ($attributes->name == "updated") + $contact["avatar-date"] = $attributes->textContent; } if (($width > 0) AND ($href != "")) $avatarlist[$width] = $href; @@ -65,7 +108,26 @@ class dfrn2 { } if ($r AND !$onlyfetch) { + + // When was the last change to name or uri? + $name_element = $xpath->query($element."/atom:name", $context)->item(0); + foreach($name_element->attributes AS $attributes) + if ($attributes->name == "updated") + $contact["name-date"] = $attributes->textContent; + + + $link_element = $xpath->query($element."/atom:link", $context)->item(0); + foreach($link_element->attributes AS $attributes) + if ($attributes->name == "updated") + $contact["uri-date"] = $attributes->textContent; + + // is it a public forum? Private forums aren't supported by now with this method + $contact["forum"] = intval($xpath->evaluate($element.'/dfrn:community/text()', $context)->item(0)->nodeValue); + // Update contact data + $value = $xpath->evaluate($element.'/dfrn:handle/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["addr"] = $value; $value = $xpath->evaluate($element.'/poco:displayName/text()', $context)->item(0)->nodeValue; if ($value != "") @@ -83,13 +145,54 @@ class dfrn2 { if ($value != "") $contact["location"] = $value; - /// @todo - /// poco:birthday - /// poco:utcOffset - /// poco:updated - /// poco:ims - /// poco:tags + /// @todo Add support for the following fields that we don't support by now in the contact table: + /// - poco:utcOffset + /// - poco:ims + /// - poco:urls + /// - poco:locality + /// - poco:region + /// - poco:country + // Save the keywords into the contact table + $tags = array(); + $tagelements = $xpath->evaluate($element.'/poco:tags/text()', $context); + foreach($tagelements AS $tag) + $tags[$tag->nodeValue] = $tag->nodeValue; + + if (count($tags)) + $contact["keywords"] = implode(", ", $tags); + + // "dfrn:birthday" contains the birthday converted to UTC + $old_bdyear = $contact["bdyear"]; + + $birthday = $xpath->evaluate($element.'/dfrn:birthday/text()', $context)->item(0)->nodeValue; + + if (strtotime($birthday) > time()) { + $bd_timestamp = strtotime($birthday); + + $contact["bdyear"] = date("Y", $bd_timestamp); + } + + // "poco:birthday" is the birthday in the format "yyyy-mm-dd" + $value = $xpath->evaluate($element.'/poco:birthday/text()', $context)->item(0)->nodeValue; + + if (!in_array($value, array("", "0000-00-00"))) { + $bdyear = date("Y"); + $value = str_replace("0000", $bdyear, $value); + + if (strtotime($value) < time()) { + $value = str_replace($bdyear, $bdyear + 1, $value); + $bdyear = $bdyear + 1; + } + + $contact["bd"] = $value; + } + + if ($old_bdyear != $contact["bdyear"]) + self::birthday_event($contact, $birthday; + +print_r($contact); +die(); /* if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { @@ -162,13 +265,7 @@ class dfrn2 { $item_id = 0; - // Reverse the order of the entries - $entrylist = array(); - - foreach ($entries AS $entry) - $entrylist[] = $entry; - - foreach (array_reverse($entrylist) AS $entry) { + foreach ($entries AS $entry) { $item = $header; From 613f6b9b32e5c8370bb236caba03a82c09625239 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 27 Jan 2016 20:06:23 +0100 Subject: [PATCH 003/273] Just some more code :-) --- include/import-dfrn.php | 97 +++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 5a35829263..b3a8dbaf86 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -25,13 +25,13 @@ define("NS_OSTATUS", "http://ostatus.org/schema/1.0"); define("NS_STATUSNET", "http://status.net/schema/api/1/"); class dfrn2 { - /** - * @brief Add new birthday event for this person - * - * @param array $contact Contact record - * @param string $birthday Birthday of the contact - * - */ + /** + * @brief Add new birthday event for this person + * + * @param array $contact Contact record + * @param string $birthday Birthday of the contact + * + */ private function birthday_event($contact, $birthday) { logger('updating birthday: '.$birthday.' for contact '.$contact['id']); @@ -54,25 +54,27 @@ class dfrn2 { ); } - /** - * @brief Fetch the author data from head or entry items - * - * @param object $xpath XPath object - * @param object $context In which context should the data be searched - * @param array $importer Record of the importer contact - * @param string $element Element name from which the data is fetched - * @param array $contact The updated contact record of the author - * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well - * + /** + * @brief Fetch the author data from head or entry items + * + * @param object $xpath XPath object + * @param object $context In which context should the data be searched + * @param array $importer Record of the importer contact + * @param string $element Element name from which the data is fetched + * @param array $contact The updated contact record of the author + * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well + * * @return Returns an array with relevant data of the author - */ + */ private function fetchauthor($xpath, $context, $importer, $element, &$contact, $onlyfetch) { $author = array(); $author["name"] = $xpath->evaluate($element.'/atom:name/text()', $context)->item(0)->nodeValue; $author["link"] = $xpath->evaluate($element.'/atom:uri/text()', $context)->item(0)->nodeValue; - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", + $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, + `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd` + FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); if ($r) { @@ -115,15 +117,11 @@ class dfrn2 { if ($attributes->name == "updated") $contact["name-date"] = $attributes->textContent; - $link_element = $xpath->query($element."/atom:link", $context)->item(0); foreach($link_element->attributes AS $attributes) if ($attributes->name == "updated") $contact["uri-date"] = $attributes->textContent; - // is it a public forum? Private forums aren't supported by now with this method - $contact["forum"] = intval($xpath->evaluate($element.'/dfrn:community/text()', $context)->item(0)->nodeValue); - // Update contact data $value = $xpath->evaluate($element.'/dfrn:handle/text()', $context)->item(0)->nodeValue; if ($value != "") @@ -188,37 +186,49 @@ class dfrn2 { $contact["bd"] = $value; } - if ($old_bdyear != $contact["bdyear"]) - self::birthday_event($contact, $birthday; + //if ($old_bdyear != $contact["bdyear"]) + // self::birthday_event($contact, $birthday); -print_r($contact); -die(); -/* - if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { + // Get all field names + $fields = array(); + foreach ($r[0] AS $field => $data) + $fields[$field] = $data; + unset($fields["id"]); + unset($fields["uid"]); + + foreach ($fields AS $field => $data) + if ($contact[$field] != $r[0][$field]) + $update = true; + + if ($update) { logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d AND `network` = '%s'", + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', + `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s' + `avatar-date` = '%s', `name-date` = '%s', `uri-date` = '%s' + WHERE `id` = %d AND `network` = '%s'", dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), - dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS)); - + dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]), + dbesc($contact["bd"]), dbesc($contact["avatar-date"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]), + intval($contact["id"]), dbesc($contact["network"])); } - if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['photo'])) { + if ((isset($author["avatar"]) AND ($author["avatar"] != $r[0]["photo"])) OR + ($contact["avatar-date"] != $r[0]["avatar-date"])) { logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); - $photos = import_profile_photo($author["author-avatar"], $importer["uid"], $contact["id"]); + $photos = import_profile_photo($author["avatar"], $importer["uid"], $contact["id"]); q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d AND `network` = '%s'", - dbesc($author["author-avatar"]), dbesc($photos[1]), dbesc($photos[2]), - dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS)); + dbesc($author["avatar"]), dbesc($photos[1]), dbesc($photos[2]), + dbesc($contact["avatar-date"]), intval($contact["id"]), dbesc($contact["network"])); } -*/ - /// @todo Add the "addr" field -// $contact["generation"] = 2; -// $contact["photo"] = $author["avatar"]; -//print_r($contact); - //update_gcontact($contact); + + $contact["generation"] = 2; + $contact["photo"] = $author["avatar"]; + print_r($contact); + update_gcontact($contact); } return($author); @@ -261,6 +271,9 @@ die(); // Only the "dfrn:owner" in the head section contains all data self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", $contact, false); + // is it a public forum? Private forums aren't supported by now with this method + //$contact["forum"] = intval($xpath->evaluate($element.'/dfrn:community/text()', $context)->item(0)->nodeValue); + $entries = $xpath->query('/atom:feed/atom:entry'); $item_id = 0; From 1cdcb9fc2e9abc97167a6b004d773172e4166eb7 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 29 Jan 2016 17:42:38 +0100 Subject: [PATCH 004/273] DFRN: Entry import could work now, first steps for mails --- include/Photo.php | 5 +- include/import-dfrn.php | 533 +++++++++++++++++++++++----------------- 2 files changed, 315 insertions(+), 223 deletions(-) diff --git a/include/Photo.php b/include/Photo.php index 3f1608d3ec..91fce55a86 100644 --- a/include/Photo.php +++ b/include/Photo.php @@ -726,10 +726,11 @@ function guess_image_type($filename, $fromcurl=false) { * @param string $avatar Link to avatar picture * @param int $uid User id of contact owner * @param int $cid Contact id + * @param bool $force force picture update * * @return array Returns array of the different avatar sizes */ -function update_contact_avatar($avatar,$uid,$cid) { +function update_contact_avatar($avatar,$uid,$cid, $force = false) { $r = q("SELECT `avatar`, `photo`, `thumb`, `micro` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid)); if (!$r) @@ -737,7 +738,7 @@ function update_contact_avatar($avatar,$uid,$cid) { else $data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]); - if ($r[0]["avatar"] != $avatar) { + if (($r[0]["avatar"] != $avatar) OR $force) { $photos = import_profile_photo($avatar,$uid,$cid, true); if ($photos) { diff --git a/include/import-dfrn.php b/include/import-dfrn.php index b3a8dbaf86..c934380357 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -109,6 +109,8 @@ class dfrn2 { $author["avatar"] = current($avatarlist); } +$onlyfetch = true; // Test + if ($r AND !$onlyfetch) { // When was the last change to name or uri? @@ -186,8 +188,8 @@ class dfrn2 { $contact["bd"] = $value; } - //if ($old_bdyear != $contact["bdyear"]) - // self::birthday_event($contact, $birthday); + if ($old_bdyear != $contact["bdyear"]) + self::birthday_event($contact, $birthday); // Get all field names $fields = array(); @@ -214,26 +216,301 @@ class dfrn2 { intval($contact["id"]), dbesc($contact["network"])); } - if ((isset($author["avatar"]) AND ($author["avatar"] != $r[0]["photo"])) OR - ($contact["avatar-date"] != $r[0]["avatar-date"])) { - logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); - - $photos = import_profile_photo($author["avatar"], $importer["uid"], $contact["id"]); - - q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d AND `network` = '%s'", - dbesc($author["avatar"]), dbesc($photos[1]), dbesc($photos[2]), - dbesc($contact["avatar-date"]), intval($contact["id"]), dbesc($contact["network"])); - } + update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"], ($contact["avatar-date"] != $r[0]["avatar-date"])); $contact["generation"] = 2; $contact["photo"] = $author["avatar"]; - print_r($contact); update_gcontact($contact); } return($author); } + private function transform_activity($xpath, $activity, $element) { + if (!is_object($activity)) + return ""; + + $obj_doc = new DOMDocument('1.0', 'utf-8'); + $obj_doc->formatOutput = true; + + $obj_element = $obj_doc->createElementNS(NS_ATOM, $element); + + $activity_type = $xpath->query('activity:object-type/text()', $activity)->item(0)->nodeValue; + xml_add_element($obj_doc, $obj_element, "type", $activity_type); + + $id = $xpath->query('atom:id', $activity)->item(0); + if (is_object($id)) + $obj_element->appendChild($obj_doc->importNode($id, true)); + + $title = $xpath->query('atom:title', $activity)->item(0); + if (is_object($title)) + $obj_element->appendChild($obj_doc->importNode($title, true)); + + $link = $xpath->query('atom:link', $activity)->item(0); + if (is_object($link)) + $obj_element->appendChild($obj_doc->importNode($link, true)); + + $content = $xpath->query('atom:content', $activity)->item(0); + if (is_object($content)) + $obj_element->appendChild($obj_doc->importNode($content, true)); + + $obj_doc->appendChild($obj_element); + + $objxml = $obj_doc->saveXML($obj_element); + + // @todo This isn't totally clean. We should find a way to transform the namespaces + $objxml = str_replace('<'.$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml); + return($objxml); + } + + private function process_mail($header, $xpath, $mail, $importer, $contact) { + + $msg = array(); + $msg["uid"] = $importer['importer_uid']; + $msg["from-name"] = $xpath->query('dfrn:sender/dfrn:name/text()', $mail)->item(0)->nodeValue; + $msg["from-url"] = $xpath->query('dfrn:sender/dfrn:uri/text()', $mail)->item(0)->nodeValue; + $msg["from-photo"] = $xpath->query('dfrn:sender/dfrn:avatar/text()', $mail)->item(0)->nodeValue; + $msg["contact-id"] = $importer["id"]; + $msg["uri"] = $xpath->query('dfrn:id/text()', $mail)->item(0)->nodeValue; + $msg["parent-uri"] = $xpath->query('dfrn:in-reply-to/text()', $mail)->item(0)->nodeValue; + $msg["created"] = $xpath->query('dfrn:sentdate/text()', $mail)->item(0)->nodeValue; + $msg["title"] = $xpath->query('dfrn:subject/text()', $mail)->item(0)->nodeValue; + $msg["body"] = $xpath->query('dfrn:content/text()', $mail)->item(0)->nodeValue; + $msg["seen"] = 0; + $msg["replied"] = 0; + + dbesc_array($msg); + + //$r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) + // . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" ); + +print_r($msg); + + // send notifications. + + require_once('include/enotify.php'); + + $notif_params = array( + 'type' => NOTIFY_MAIL, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $msg, + 'source_name' => $msg['from-name'], + 'source_link' => $importer['url'], + 'source_photo' => $importer['thumb'], + 'verb' => ACTIVITY_POST, + 'otype' => 'mail' + ); + +// notification($notif_params); +print_r($notif_params); + + } + + private function process_suggestion($header, $xpath, $suggestion, $importer, $contact) { + } + + private function process_relocation($header, $xpath, $relocation, $importer, $contact) { + } + + private function process_entry($header, $xpath, $entry, $importer, $contact) { + $item = $header; + + // Fetch the owner + $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", $contact, true); + + $item["owner-name"] = $owner["name"]; + $item["owner-link"] = $owner["link"]; + $item["owner-avatar"] = $owner["avatar"]; + + if ($header["contact-id"] != $owner["contact-id"]) + $item["contact-id"] = $owner["contact-id"]; + + if (($header["network"] != $owner["network"]) AND ($owner["network"] != "")) + $item["network"] = $owner["network"]; + + // fetch the author + $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", $contact, true); + + $item["author-name"] = $author["name"]; + $item["author-link"] = $author["link"]; + $item["author-avatar"] = $author["avatar"]; + + if ($header["contact-id"] != $author["contact-id"]) + $item["contact-id"] = $author["contact-id"]; + + if (($header["network"] != $author["network"]) AND ($author["network"] != "")) + $item["network"] = $author["network"]; + + // Now get the item + $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["uri"])); + if ($r) { + //logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); + //return false; + } + + // Is it a reply? + $inreplyto = $xpath->query('thr:in-reply-to', $entry); + if (is_object($inreplyto->item(0))) { + $objecttype = ACTIVITY_OBJ_COMMENT; + $item["type"] = 'remote-comment'; + $item["gravity"] = GRAVITY_COMMENT; + + foreach($inreplyto->item(0)->attributes AS $attributes) { + if ($attributes->name == "ref") + $item["parent-uri"] = $attributes->textContent; + } + } else { + $objecttype = ACTIVITY_OBJ_NOTE; + $item["parent-uri"] = $item["uri"]; + } + + $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + + $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; + $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; + + $item["body"] = $xpath->query('dfrn:env/text()', $entry)->item(0)->nodeValue; + $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]); + // make sure nobody is trying to sneak some html tags by us + $item["body"] = notags(base64url_decode($item["body"])); + + $item["body"] = limit_body_size($item["body"]); + + /// @todo Do we need the old check for HTML elements? + + // We don't need the content element since "dfrn:env" is always present + //$item["body"] = $xpath->query('atom:content/text()', $entry)->item(0)->nodeValue; + + $item["last-child"] = $xpath->query('dfrn:comment-allow/text()', $entry)->item(0)->nodeValue; + $item["location"] = $xpath->query('dfrn:location/text()', $entry)->item(0)->nodeValue; + + $georsspoint = $xpath->query('georss:point', $entry); + if ($georsspoint) + $item["coord"] = $georsspoint->item(0)->nodeValue; + + $item["private"] = $xpath->query('dfrn:private/text()', $entry)->item(0)->nodeValue; + + $item["extid"] = $xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue; + + if ($xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue == "true") + $item["bookmark"] = true; + + $notice_info = $xpath->query('statusnet:notice_info', $entry); + if ($notice_info AND ($notice_info->length > 0)) { + foreach($notice_info->item(0)->attributes AS $attributes) { + if ($attributes->name == "source") + $item["app"] = strip_tags($attributes->textContent); + } + } + + $item["guid"] = $xpath->query('dfrn:diaspora_guid/text()', $entry)->item(0)->nodeValue; + + // We store the data from "dfrn:diaspora_signature" in a later step. See some lines below + $signature = $xpath->query('dfrn:diaspora_signature/text()', $entry)->item(0)->nodeValue; + + $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; + + if ($xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue != "") + $objecttype = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; + + $item["object-type"] = $objecttype; + + // I have the feeling that we don't do anything with this data + $object = $xpath->query('activity:object', $entry)->item(0); + $item["object"] = self::transform_activity($xpath, $object, "object"); + + // Could someone explain what this is for? + $target = $xpath->query('activity:target', $entry)->item(0); + $item["target"] = self::transform_activity($xpath, $target, "target"); + + $categories = $xpath->query('atom:category', $entry); + if ($categories) { + foreach ($categories AS $category) { + foreach($category->attributes AS $attributes) + if ($attributes->name == "term") { + $term = $attributes->textContent; + if(strlen($item["tag"])) + $item["tag"] .= ','; + + $item["tag"] .= "#[url=".$a->get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + } + } + } + + $enclosure = ""; + + $links = $xpath->query('atom:link', $entry); + if ($links) { + $rel = ""; + $href = ""; + $type = ""; + $length = "0"; + $title = ""; + foreach ($links AS $link) { + foreach($link->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "rel") + $rel = $attributes->textContent; + if ($attributes->name == "type") + $type = $attributes->textContent; + if ($attributes->name == "length") + $length = $attributes->textContent; + if ($attributes->name == "title") + $title = $attributes->textContent; + } + if (($rel != "") AND ($href != "")) + switch($rel) { + case "alternate": + $item["plink"] = $href; + break; + case "enclosure": + $enclosure = $href; + if(strlen($item["attach"])) + $item["attach"] .= ','; + + $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; + break; + } + } + } + + print_r($item); + //$item_id = item_store($item); + + return; + + if (!$item_id) { + logger("Error storing item", LOGGER_DEBUG); + return false; + } else { + logger("Item was stored with id ".$item_id, LOGGER_DEBUG); + + if ($signature) { + $signature = json_decode(base64_decode($signature)); + + // Check for falsely double encoded signatures + $signature->signature = diaspora_repair_signature($signature->signature, $signature->signer); + + // Store it in the "sign" table where we will read it for comments that we relay to Diaspora + q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')", + intval($item_id), + dbesc($signature->signed_text), + dbesc($signature->signature), + dbesc($signature->signer) + ); + } + } + return $item_id; + } + function import($xml,$importer,&$contact, &$hub) { $a = get_app(); @@ -269,218 +546,32 @@ class dfrn2 { // Update the contact table if the data has changed // Only the "dfrn:owner" in the head section contains all data - self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", $contact, false); + $dfrn_owner = self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", $contact, false); // is it a public forum? Private forums aren't supported by now with this method - //$contact["forum"] = intval($xpath->evaluate($element.'/dfrn:community/text()', $context)->item(0)->nodeValue); + $forum = intval($xpath->evaluate('/atom:feed/dfrn:community/text()', $context)->item(0)->nodeValue); + + if ($forum AND ($dfrn_owner["contact-id"] != 0)) + q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d", + intval($forum), intval($forum), + intval($dfrn_owner["contact-id"]) + ); + + $mails = $xpath->query('/atom:feed/dfrn:mail'); + foreach ($mails AS $mail) + self::process_mail($header, $xpath, $mail, $importer, $contact); + + $suggestions = $xpath->query('/atom:feed/dfrn:suggest'); + foreach ($suggestions AS $suggestion) + self::process_suggestion($header, $xpath, $suggestion, $importer, $contact); + + $relocations = $xpath->query('/atom:feed/dfrn:relocate'); + foreach ($relocations AS $relocation) + self::process_relocation($header, $xpath, $relocation, $importer, $contact); $entries = $xpath->query('/atom:feed/atom:entry'); - - $item_id = 0; - - foreach ($entries AS $entry) { - - $item = $header; - - $mention = false; - - // Fetch the owner - $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", $contact, true); - - $item["owner-name"] = $owner["name"]; - $item["owner-link"] = $owner["link"]; - $item["owner-avatar"] = $owner["avatar"]; - - if ($header["contact-id"] != $owner["contact-id"]) - $item["contact-id"] = $owner["contact-id"]; - - if (($header["network"] != $owner["network"]) AND ($owner["network"] != "")) - $item["network"] = $owner["network"]; - - // fetch the author - $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", $contact, true); - - $item["author-name"] = $author["name"]; - $item["author-link"] = $author["link"]; - $item["author-avatar"] = $author["avatar"]; - - if ($header["contact-id"] != $author["contact-id"]) - $item["contact-id"] = $author["contact-id"]; - - if (($header["network"] != $author["network"]) AND ($author["network"] != "")) - $item["network"] = $author["network"]; - - // Now get the item - $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["uri"])); - if ($r) { - //logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); - //continue; - } - - // Is it a reply? - $inreplyto = $xpath->query('thr:in-reply-to', $entry); - if (is_object($inreplyto->item(0))) { - $objecttype = ACTIVITY_OBJ_COMMENT; - $item["type"] = 'remote-comment'; - $item["gravity"] = GRAVITY_COMMENT; - - foreach($inreplyto->item(0)->attributes AS $attributes) { - if ($attributes->name == "ref") - $item["parent-uri"] = $attributes->textContent; - } - } else { - $objecttype = ACTIVITY_OBJ_NOTE; - $item["parent-uri"] = $item["uri"]; - } - - $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; - - $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; - $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; - - $item["body"] = $xpath->query('dfrn:env/text()', $entry)->item(0)->nodeValue; - $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]); - // make sure nobody is trying to sneak some html tags by us - $item["body"] = notags(base64url_decode($item["body"])); - - $item["body"] = limit_body_size($item["body"]); - - /// @todo Do we need the old check for HTML elements? - - // We don't need the content element since "dfrn:env" is always present - //$item["body"] = $xpath->query('atom:content/text()', $entry)->item(0)->nodeValue; - - $item["last-child"] = $xpath->query('dfrn:comment-allow/text()', $entry)->item(0)->nodeValue; - $item["location"] = $xpath->query('dfrn:location/text()', $entry)->item(0)->nodeValue; - - $georsspoint = $xpath->query('georss:point', $entry); - if ($georsspoint) - $item["coord"] = $georsspoint->item(0)->nodeValue; - - $item["private"] = $xpath->query('dfrn:private/text()', $entry)->item(0)->nodeValue; - - $item["extid"] = $xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue; - - if ($xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue == "true") - $item["bookmark"] = true; - - $notice_info = $xpath->query('statusnet:notice_info', $entry); - if ($notice_info AND ($notice_info->length > 0)) { - foreach($notice_info->item(0)->attributes AS $attributes) { - if ($attributes->name == "source") - $item["app"] = strip_tags($attributes->textContent); - } - } - - $item["guid"] = $xpath->query('dfrn:diaspora_guid/text()', $entry)->item(0)->nodeValue; - - // dfrn:diaspora_signature - - $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; - - if ($xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue != "") - $objecttype = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; - - $item["object-type"] = $objecttype; - - // activity:object - - // activity:target - - $categories = $xpath->query('atom:category', $entry); - if ($categories) { - foreach ($categories AS $category) { - foreach($category->attributes AS $attributes) - if ($attributes->name == "term") { - $term = $attributes->textContent; - if(strlen($item["tag"])) - $item["tag"] .= ','; - $item["tag"] .= "#[url=".$a->get_baseurl()."/search?tag=".$term."]".$term."[/url]"; - } - } - } - - $enclosure = ""; - - $links = $xpath->query('atom:link', $entry); - if ($links) { - $rel = ""; - $href = ""; - $type = ""; - $length = "0"; - $title = ""; - foreach ($links AS $link) { - foreach($link->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "rel") - $rel = $attributes->textContent; - if ($attributes->name == "type") - $type = $attributes->textContent; - if ($attributes->name == "length") - $length = $attributes->textContent; - if ($attributes->name == "title") - $title = $attributes->textContent; - } - if (($rel != "") AND ($href != "")) - switch($rel) { - case "alternate": - $item["plink"] = $href; - break; - case "enclosure": - $enclosure = $href; - if(strlen($item["attach"])) - $item["attach"] .= ','; - - $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; - break; - case "mentioned": - // Notification check - if ($importer["nurl"] == normalise_link($href)) - $mention = true; - break; - } - } - } - - print_r($item); -/* - if (!$item_id) { - logger("Error storing item", LOGGER_DEBUG); - continue; - } - - logger("Item was stored with id ".$item_id, LOGGER_DEBUG); - $item["id"] = $item_id; -*/ - -/* - if ($mention) { - $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($item['uid'])); - $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($item_id)); - - notification(array( - 'type' => NOTIFY_TAGSELF, - 'notify_flags' => $u[0]["notify-flags"], - 'language' => $u[0]["language"], - 'to_name' => $u[0]["username"], - 'to_email' => $u[0]["email"], - 'uid' => $item["uid"], - 'item' => $item, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($item_id)), - 'source_name' => $item["author-name"], - 'source_link' => $item["author-link"], - 'source_photo' => $item["author-avatar"], - 'verb' => ACTIVITY_TAG, - 'otype' => 'item', - 'parent' => $r[0]["parent"] - )); - } -*/ - } + foreach ($entries AS $entry) + self::process_entry($header, $xpath, $entry, $importer, $contact); } } ?> From decaac6c31226fcbfd064f894e14f5c3b2405813 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 29 Jan 2016 23:14:01 +0100 Subject: [PATCH 005/273] DFRN-Import is now nearly complete, changed namespace constants --- include/dfrn.php | 18 +-- include/import-dfrn.php | 249 +++++++++++++++++++++++++++++++++------- include/ostatus.php | 73 ++++++------ 3 files changed, 248 insertions(+), 92 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 883afe15f7..50d78de3c4 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -386,15 +386,15 @@ class dfrn { $root = $doc->createElementNS(NS_ATOM, 'feed'); $doc->appendChild($root); - $root->setAttribute("xmlns:thr", NS_THR); - $root->setAttribute("xmlns:at", "http://purl.org/atompub/tombstones/1.0"); - $root->setAttribute("xmlns:media", NS_MEDIA); - $root->setAttribute("xmlns:dfrn", "http://purl.org/macgirvin/dfrn/1.0"); - $root->setAttribute("xmlns:activity", NS_ACTIVITY); - $root->setAttribute("xmlns:georss", NS_GEORSS); - $root->setAttribute("xmlns:poco", NS_POCO); - $root->setAttribute("xmlns:ostatus", NS_OSTATUS); - $root->setAttribute("xmlns:statusnet", NS_STATUSNET); + $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $root->setAttribute("xmlns:at", NAMESPACE_TOMB); + $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $root->setAttribute("xmlns:dfrn", NAMESPACE_DFRN); + $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $root->setAttribute("xmlns:poco", NAMESPACE_POCO); + $root->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, "title", $owner["name"]); diff --git a/include/import-dfrn.php b/include/import-dfrn.php index c934380357..3265eb6ff5 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -263,57 +263,215 @@ $onlyfetch = true; // Test return($objxml); } - private function process_mail($header, $xpath, $mail, $importer, $contact) { + private function process_mail($xpath, $mail, $importer) { $msg = array(); $msg["uid"] = $importer['importer_uid']; $msg["from-name"] = $xpath->query('dfrn:sender/dfrn:name/text()', $mail)->item(0)->nodeValue; $msg["from-url"] = $xpath->query('dfrn:sender/dfrn:uri/text()', $mail)->item(0)->nodeValue; $msg["from-photo"] = $xpath->query('dfrn:sender/dfrn:avatar/text()', $mail)->item(0)->nodeValue; - $msg["contact-id"] = $importer["id"]; + $msg["contact-id"] = $importer["id"]; $msg["uri"] = $xpath->query('dfrn:id/text()', $mail)->item(0)->nodeValue; $msg["parent-uri"] = $xpath->query('dfrn:in-reply-to/text()', $mail)->item(0)->nodeValue; $msg["created"] = $xpath->query('dfrn:sentdate/text()', $mail)->item(0)->nodeValue; $msg["title"] = $xpath->query('dfrn:subject/text()', $mail)->item(0)->nodeValue; $msg["body"] = $xpath->query('dfrn:content/text()', $mail)->item(0)->nodeValue; - $msg["seen"] = 0; - $msg["replied"] = 0; + $msg["seen"] = 0; + $msg["replied"] = 0; dbesc_array($msg); - //$r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) - // . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" ); + $r = dbq("INSERT INTO `mail` (`".implode("`, `", array_keys($msg))."`) VALUES ('".implode("', '", array_values($msg))."')"); -print_r($msg); + // send notifications. - // send notifications. + $notif_params = array( + 'type' => NOTIFY_MAIL, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $msg, + 'source_name' => $msg['from-name'], + 'source_link' => $importer['url'], + 'source_photo' => $importer['thumb'], + 'verb' => ACTIVITY_POST, + 'otype' => 'mail' + ); - require_once('include/enotify.php'); + notification($notif_params); + } - $notif_params = array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $msg, - 'source_name' => $msg['from-name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' - ); + private function process_suggestion($xpath, $suggestion, $importer) { -// notification($notif_params); -print_r($notif_params); + $suggest = array(); + $suggest["uid"] = $importer["importer_uid"]; + $suggest["cid"] = $importer["id"]; + $suggest["url"] = $xpath->query('dfrn:url/text()', $suggestion)->item(0)->nodeValue; + $suggest["name"] = $xpath->query('dfrn:name/text()', $suggestion)->item(0)->nodeValue; + $suggest["photo"] = $xpath->query('dfrn:photo/text()', $suggestion)->item(0)->nodeValue; + $suggest["request"] = $xpath->query('dfrn:request/text()', $suggestion)->item(0)->nodeValue; + $suggest["note"] = $xpath->query('dfrn:note/text()', $suggestion)->item(0)->nodeValue; + + // Does our member already have a friend matching this description? + + $r = q("SELECT `id` FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc($suggest["name"]), + dbesc(normalise_link($suggest["url"])), + intval($suggest["uid"]) + ); + if(count($r)) + return false; + + // Do we already have an fcontact record for this person? + + $fid = 0; + $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($suggest["url"]), + dbesc($suggest["name"]), + dbesc($suggest["request"]) + ); + if(count($r)) { + $fid = $r[0]["id"]; + + // OK, we do. Do we already have an introduction for this person ? + $r = q("SELECT `id` FROM `intro` WHERE `uid` = %d AND `fid` = %d LIMIT 1", + intval($suggest["uid"]), + intval($fid) + ); + if(count($r)) + return false; + } + if(!$fid) + $r = q("INSERT INTO `fcontact` (`name`,`url`,`photo`,`request`) VALUES ('%s', '%s', '%s', '%s')", + dbesc($suggest["name"]), + dbesc($suggest["url"]), + dbesc($suggest["photo"]), + dbesc($suggest["request"]) + ); + $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($suggest["url"]), + dbesc($suggest["name"]), + dbesc($suggest["request"]) + ); + if(count($r)) + $fid = $r[0]["id"]; + else + // database record did not get created. Quietly give up. + return false; + + + $hash = random_string(); + + $r = q("INSERT INTO `intro` (`uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked`) + VALUES(%d, %d, %d, '%s', '%s', '%s', %d)", + intval($suggest["uid"]), + intval($fid), + intval($suggest["cid"]), + dbesc($suggest["body"]), + dbesc($hash), + dbesc(datetime_convert()), + intval(0) + ); + + notification(array( + 'type' => NOTIFY_SUGGEST, + 'notify_flags' => $importer["notify-flags"], + 'language' => $importer["language"], + 'to_name' => $importer["username"], + 'to_email' => $importer["email"], + 'uid' => $importer["importer_uid"], + 'item' => $suggest, + 'link' => App::get_baseurl()."/notifications/intros", + 'source_name' => $importer["name"], + 'source_link' => $importer["url"], + 'source_photo' => $importer["photo"], + 'verb' => ACTIVITY_REQ_FRIEND, + 'otype' => "intro" + )); + + return true; } - private function process_suggestion($header, $xpath, $suggestion, $importer, $contact) { - } + private function process_relocation($xpath, $relocation, $importer) { - private function process_relocation($header, $xpath, $relocation, $importer, $contact) { + $relocate = array(); + $relocate["uid"] = $importer["importer_uid"]; + $relocate["cid"] = $importer["id"]; + $relocate["url"] = $xpath->query('dfrn:url/text()', $relocation)->item(0)->nodeValue; + $relocate["name"] = $xpath->query('dfrn:name/text()', $relocation)->item(0)->nodeValue; + $relocate["photo"] = $xpath->query('dfrn:photo/text()', $relocation)->item(0)->nodeValue; + $relocate["thumb"] = $xpath->query('dfrn:thumb/text()', $relocation)->item(0)->nodeValue; + $relocate["micro"] = $xpath->query('dfrn:micro/text()', $relocation)->item(0)->nodeValue; + $relocate["request"] = $xpath->query('dfrn:request/text()', $relocation)->item(0)->nodeValue; + $relocate["confirm"] = $xpath->query('dfrn:confirm/text()', $relocation)->item(0)->nodeValue; + $relocate["notify"] = $xpath->query('dfrn:notify/text()', $relocation)->item(0)->nodeValue; + $relocate["poll"] = $xpath->query('dfrn:poll/text()', $relocation)->item(0)->nodeValue; + $relocate["sitepubkey"] = $xpath->query('dfrn:sitepubkey/text()', $relocation)->item(0)->nodeValue; + + // update contact + $r = q("SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d;", + intval($importer["id"]), + intval($importer["importer_uid"])); + if (!$r) + return false; + + $old = $r[0]; + + $x = q("UPDATE `contact` SET + `name` = '%s', + `photo` = '%s', + `thumb` = '%s', + `micro` = '%s', + `url` = '%s', + `nurl` = '%s', + `request` = '%s', + `confirm` = '%s', + `notify` = '%s', + `poll` = '%s', + `site-pubkey` = '%s' + WHERE `id` = %d AND `uid` = %d;", + dbesc($relocate["name"]), + dbesc($relocate["photo"]), + dbesc($relocate["thumb"]), + dbesc($relocate["micro"]), + dbesc($relocate["url"]), + dbesc(normalise_link($relocate["url"])), + dbesc($relocate["request"]), + dbesc($relocate["confirm"]), + dbesc($relocate["notify"]), + dbesc($relocate["poll"]), + dbesc($relocate["sitepubkey"]), + intval($importer["id"]), + intval($importer["importer_uid"])); + + if ($x === false) + return false; + + // update items + $fields = array( + 'owner-link' => array($old["url"], $relocate["url"]), + 'author-link' => array($old["url"], $relocate["url"]), + 'owner-avatar' => array($old["photo"], $relocate["photo"]), + 'author-avatar' => array($old["photo"], $relocate["photo"]), + ); + foreach ($fields as $n=>$f){ + $x = q("UPDATE `item` SET `%s` = '%s' WHERE `%s` = '%s' AND `uid` = %d", + $n, dbesc($f[1]), + $n, dbesc($f[0]), + intval($importer["importer_uid"])); + if ($x === false) + return false; + } + + /// @TODO + /// merge with current record, current contents have priority + /// update record, set url-updated + /// update profile photos + /// schedule a scan? + return true; } private function process_entry($header, $xpath, $entry, $importer, $contact) { @@ -511,7 +669,11 @@ print_r($notif_params); return $item_id; } - function import($xml,$importer,&$contact, &$hub) { + private function process_deletion($header, $xpath, $entry, $importer, $contact) { + die("blubb"); + } + + function import($xml,$importer) { $a = get_app(); @@ -524,16 +686,19 @@ print_r($notif_params); @$doc->loadXML($xml); $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom"); - $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0"); - $xpath->registerNamespace('at', "http://purl.org/atompub/tombstones/1.0"); - $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia"); - $xpath->registerNamespace('dfrn', "http://purl.org/macgirvin/dfrn/1.0"); - $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/"); - $xpath->registerNamespace('georss', "http://www.georss.org/georss"); - $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0"); - $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0"); - $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/"); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('at', NAMESPACE_TOMB); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('dfrn', NAMESPACE_DFRN); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); + + $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `self`", intval($importer["uid"])); + $contact = $r[0]; $header = array(); $header["uid"] = $importer["uid"]; @@ -559,15 +724,15 @@ print_r($notif_params); $mails = $xpath->query('/atom:feed/dfrn:mail'); foreach ($mails AS $mail) - self::process_mail($header, $xpath, $mail, $importer, $contact); + self::process_mail($xpath, $mail, $importer); $suggestions = $xpath->query('/atom:feed/dfrn:suggest'); foreach ($suggestions AS $suggestion) - self::process_suggestion($header, $xpath, $suggestion, $importer, $contact); + self::process_suggestion($xpath, $suggestion, $importer); $relocations = $xpath->query('/atom:feed/dfrn:relocate'); foreach ($relocations AS $relocation) - self::process_relocation($header, $xpath, $relocation, $importer, $contact); + self::process_relocation($xpath, $relocation, $importer); $entries = $xpath->query('/atom:feed/atom:entry'); foreach ($entries AS $entry) diff --git a/include/ostatus.php b/include/ostatus.php index 37b308db70..caaeec84f7 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -17,15 +17,6 @@ define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes define('OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS', 14400); // given in minutes -define("NS_ATOM", "http://www.w3.org/2005/Atom"); -define("NS_THR", "http://purl.org/syndication/thread/1.0"); -define("NS_GEORSS", "http://www.georss.org/georss"); -define("NS_ACTIVITY", "http://activitystrea.ms/spec/1.0/"); -define("NS_MEDIA", "http://purl.org/syndication/atommedia"); -define("NS_POCO", "http://portablecontacts.net/spec/1.0"); -define("NS_OSTATUS", "http://ostatus.org/schema/1.0"); -define("NS_STATUSNET", "http://status.net/schema/api/1/"); - function ostatus_check_follow_friends() { $r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''"); @@ -193,14 +184,14 @@ function ostatus_salmon_author($xml, $importer) { @$doc->loadXML($xml); $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom"); - $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0"); - $xpath->registerNamespace('georss', "http://www.georss.org/georss"); - $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/"); - $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia"); - $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0"); - $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0"); - $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/"); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); $entries = $xpath->query('/atom:entry'); @@ -224,14 +215,14 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { @$doc->loadXML($xml); $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom"); - $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0"); - $xpath->registerNamespace('georss', "http://www.georss.org/georss"); - $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/"); - $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia"); - $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0"); - $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0"); - $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/"); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); $gub = ""; $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes; @@ -1120,16 +1111,16 @@ function ostatus_format_picture_post($body) { function ostatus_add_header($doc, $owner) { $a = get_app(); - $root = $doc->createElementNS(NS_ATOM, 'feed'); + $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); $doc->appendChild($root); - $root->setAttribute("xmlns:thr", NS_THR); - $root->setAttribute("xmlns:georss", NS_GEORSS); - $root->setAttribute("xmlns:activity", NS_ACTIVITY); - $root->setAttribute("xmlns:media", NS_MEDIA); - $root->setAttribute("xmlns:poco", NS_POCO); - $root->setAttribute("xmlns:ostatus", NS_OSTATUS); - $root->setAttribute("xmlns:statusnet", NS_STATUSNET); + $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $root->setAttribute("xmlns:poco", NAMESPACE_POCO); + $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); @@ -1343,15 +1334,15 @@ function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) $entry = $doc->createElement("activity:object"); $title = sprintf("New note by %s", $owner["nick"]); } else { - $entry = $doc->createElementNS(NS_ATOM, "entry"); + $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry"); - $entry->setAttribute("xmlns:thr", NS_THR); - $entry->setAttribute("xmlns:georss", NS_GEORSS); - $entry->setAttribute("xmlns:activity", NS_ACTIVITY); - $entry->setAttribute("xmlns:media", NS_MEDIA); - $entry->setAttribute("xmlns:poco", NS_POCO); - $entry->setAttribute("xmlns:ostatus", NS_OSTATUS); - $entry->setAttribute("xmlns:statusnet", NS_STATUSNET); + $entry->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $entry->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $entry->setAttribute("xmlns:poco", NAMESPACE_POCO); + $entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); $author = ostatus_add_author($doc, $owner); $entry->appendChild($author); From cd1f3cde00612a4798fc0c42fd5daa37ff393964 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 30 Jan 2016 01:20:43 +0100 Subject: [PATCH 006/273] DFRN Deletions should now work too --- include/import-dfrn.php | 143 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 12 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 3265eb6ff5..fd5b71cadb 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -1,13 +1,9 @@ <?php /* require_once("include/Contact.php"); -require_once("include/threads.php"); require_once("include/html2bbcode.php"); require_once("include/bbcode.php"); -require_once("include/items.php"); require_once("mod/share.php"); -require_once("include/enotify.php"); -require_once("include/socgraph.php"); require_once("include/Photo.php"); require_once("include/Scrape.php"); require_once("include/follow.php"); @@ -15,6 +11,13 @@ require_once("include/api.php"); require_once("mod/proxy.php"); */ +require_once("include/enotify.php"); +require_once("include/threads.php"); +require_once("include/socgraph.php"); +require_once("include/items.php"); +require_once("include/tags.php"); +require_once("include/files.php"); + define("NS_ATOM", "http://www.w3.org/2005/Atom"); define("NS_THR", "http://purl.org/syndication/thread/1.0"); define("NS_GEORSS", "http://www.georss.org/georss"); @@ -109,7 +112,7 @@ class dfrn2 { $author["avatar"] = current($avatarlist); } -$onlyfetch = true; // Test + //$onlyfetch = true; // Test if ($r AND !$onlyfetch) { @@ -640,8 +643,8 @@ $onlyfetch = true; // Test } } - print_r($item); - //$item_id = item_store($item); + //print_r($item); + $item_id = item_store($item); return; @@ -669,11 +672,121 @@ $onlyfetch = true; // Test return $item_id; } - private function process_deletion($header, $xpath, $entry, $importer, $contact) { - die("blubb"); + private function process_deletion($header, $xpath, $deletion, $importer, $contact) { + foreach($deletion->attributes AS $attributes) { + if ($attributes->name == "ref") + $uri = $attributes->textContent; + if ($attributes->name == "when") + $when = $attributes->textContent; + } + if ($when) + $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); + else + $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); + + if (!$uri OR !is_array($contact)) + return false; + + $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` + WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", + dbesc($uri), + intval($importer["uid"]), + intval($contact["id"]) + ); + if(count($r)) { + $item = $r[0]; + + if(!$item["deleted"]) + logger('deleting item '.$item["id"].' uri='.$item['uri'], LOGGER_DEBUG); + + if($item["object-type"] === ACTIVITY_OBJ_EVENT) { + logger("Deleting event ".$item["event-id"], LOGGER_DEBUG); + event_delete($item["event-id"]); + } + + if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($item["object"],false); + $xt = parse_xml_string($item["target"],false); + if($xt->type === ACTIVITY_OBJ_NOTE) { + $i = q("SELECT `id`, `contact-id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($xt->id), + intval($importer["importer_uid"]) + ); + if(count($i)) { + + // For tags, the owner cannot remove the tag on the author's copy of the post. + + $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); + $author_remove = (($item['origin'] && $item['self']) ? true : false); + $author_copy = (($item['origin']) ? true : false); + + if($owner_remove && $author_copy) + continue; + if($author_remove || $owner_remove) { + $tags = explode(',',$i[0]['tag']); + $newtags = array(); + if(count($tags)) { + foreach($tags as $tag) + if(trim($tag) !== trim($xo->body)) + $newtags[] = trim($tag); + } + q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", + dbesc(implode(',',$newtags)), + intval($i[0]['id']) + ); + create_tags_from_item($i[0]['id']); + } + } + } + } + + if($item['uri'] == $item['parent-uri']) { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + `body` = '', `title` = '' + WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc($when), + dbesc(datetime_convert()), + dbesc($item['uri']), + intval($importer['uid']) + ); + create_tags_from_itemuri($item['uri'], $importer['uid']); + create_files_from_itemuri($item['uri'], $importer['uid']); + update_thread_uri($item['uri'], $importer['uid']); + } else { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + `body` = '', `title` = '' + WHERE `uri` = '%s' AND `uid` = %d", + dbesc($when), + dbesc(datetime_convert()), + dbesc($uri), + intval($importer['uid']) + ); + create_tags_from_itemuri($uri, $importer['uid']); + create_files_from_itemuri($uri, $importer['uid']); + if($item['last-child']) { + // ensure that last-child is set in case the comment that had it just got wiped. + q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", + dbesc(datetime_convert()), + dbesc($item['parent-uri']), + intval($item['uid']) + ); + // who is the last child now? + $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d + ORDER BY `created` DESC LIMIT 1", + dbesc($item['parent-uri']), + intval($importer['uid']) + ); + if(count($r)) { + q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", + intval($r[0]['id']) + ); + } + } + } + } } - function import($xml,$importer) { + function import($xml,$importer, &$contact) { $a = get_app(); @@ -697,8 +810,10 @@ $onlyfetch = true; // Test $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); - $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `self`", intval($importer["uid"])); - $contact = $r[0]; + if (!$contact) { + $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `self`", intval($importer["uid"])); + $contact = $r[0]; + } $header = array(); $header["uid"] = $importer["uid"]; @@ -734,6 +849,10 @@ $onlyfetch = true; // Test foreach ($relocations AS $relocation) self::process_relocation($xpath, $relocation, $importer); + $deletions = $xpath->query('/atom:feed/at:deleted-entry'); + foreach ($deletions AS $deletion) + self::process_deletion($header, $xpath, $deletion, $importer, $contact); + $entries = $xpath->query('/atom:feed/atom:entry'); foreach ($entries AS $entry) self::process_entry($header, $xpath, $entry, $importer, $contact); From 69457a4a5bf5058a3982ad88094b833ece3ced4a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 30 Jan 2016 02:57:40 +0100 Subject: [PATCH 007/273] DFRN import seems to work. Improvements are possible :-) --- include/import-dfrn.php | 43 +++++++++++++++++++++++++++-------------- include/items.php | 10 ++++++++++ mod/ping.php | 6 +++++- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index fd5b71cadb..20735bd507 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -69,7 +69,7 @@ class dfrn2 { * * @return Returns an array with relevant data of the author */ - private function fetchauthor($xpath, $context, $importer, $element, &$contact, $onlyfetch) { + private function fetchauthor($xpath, $context, $importer, $element, $contact, $onlyfetch) { $author = array(); $author["name"] = $xpath->evaluate($element.'/atom:name/text()', $context)->item(0)->nodeValue; @@ -77,9 +77,8 @@ class dfrn2 { $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd` - FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", - intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), - dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); + FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); if ($r) { $contact = $r[0]; $author["contact-id"] = $r[0]["id"]; @@ -87,6 +86,7 @@ class dfrn2 { } else { $author["contact-id"] = $contact["id"]; $author["network"] = $contact["network"]; + $onlyfetch = true; } // Until now we aren't serving different sizes - but maybe later @@ -268,6 +268,8 @@ class dfrn2 { private function process_mail($xpath, $mail, $importer) { + logger("Processing mails"); + $msg = array(); $msg["uid"] = $importer['importer_uid']; $msg["from-name"] = $xpath->query('dfrn:sender/dfrn:name/text()', $mail)->item(0)->nodeValue; @@ -308,6 +310,8 @@ class dfrn2 { private function process_suggestion($xpath, $suggestion, $importer) { + logger("Processing suggestions"); + $suggest = array(); $suggest["uid"] = $importer["importer_uid"]; $suggest["cid"] = $importer["id"]; @@ -400,6 +404,8 @@ class dfrn2 { private function process_relocation($xpath, $relocation, $importer) { + logger("Processing relocations"); + $relocate = array(); $relocate["uid"] = $importer["importer_uid"]; $relocate["cid"] = $importer["id"]; @@ -478,6 +484,9 @@ class dfrn2 { } private function process_entry($header, $xpath, $entry, $importer, $contact) { + + logger("Processing entries"); + $item = $header; // Fetch the owner @@ -600,7 +609,7 @@ class dfrn2 { if(strlen($item["tag"])) $item["tag"] .= ','; - $item["tag"] .= "#[url=".$a->get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; } } } @@ -646,8 +655,6 @@ class dfrn2 { //print_r($item); $item_id = item_store($item); - return; - if (!$item_id) { logger("Error storing item", LOGGER_DEBUG); return false; @@ -672,7 +679,10 @@ class dfrn2 { return $item_id; } - private function process_deletion($header, $xpath, $deletion, $importer, $contact) { + private function process_deletion($header, $xpath, $deletion, $importer, $contact_id) { + + logger("Processing deletions"); + foreach($deletion->attributes AS $attributes) { if ($attributes->name == "ref") $uri = $attributes->textContent; @@ -684,14 +694,14 @@ class dfrn2 { else $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); - if (!$uri OR !is_array($contact)) + if (!$uri OR !$contact) return false; $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", dbesc($uri), intval($importer["uid"]), - intval($contact["id"]) + intval($contact_id) ); if(count($r)) { $item = $r[0]; @@ -788,10 +798,6 @@ class dfrn2 { function import($xml,$importer, &$contact) { - $a = get_app(); - - logger("Import DFRN message", LOGGER_DEBUG); - if ($xml == "") return; @@ -828,6 +834,13 @@ class dfrn2 { // Only the "dfrn:owner" in the head section contains all data $dfrn_owner = self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", $contact, false); + logger("Import DFRN message for user ".$importer["uid"]." from contact ".$contact["id"]." ".print_r($dfrn_owner, true)." - ".print_r($contact, true), LOGGER_DEBUG); + + //if (!$dfrn_owner["found"]) { + // logger("Author doesn't seem to be known by us. UID: ".$importer["uid"]." Contact: ".$dfrn_owner["contact-id"]." - ".print_r($dfrn_owner, true)); + // return; + //} + // is it a public forum? Private forums aren't supported by now with this method $forum = intval($xpath->evaluate('/atom:feed/dfrn:community/text()', $context)->item(0)->nodeValue); @@ -851,7 +864,7 @@ class dfrn2 { $deletions = $xpath->query('/atom:feed/at:deleted-entry'); foreach ($deletions AS $deletion) - self::process_deletion($header, $xpath, $deletion, $importer, $contact); + self::process_deletion($header, $xpath, $deletion, $importer, $dfrn_owner["contact-id"]); $entries = $xpath->query('/atom:feed/atom:entry'); foreach ($entries AS $entry) diff --git a/include/items.php b/include/items.php index 65b274019c..7df4e0c78a 100644 --- a/include/items.php +++ b/include/items.php @@ -17,6 +17,7 @@ require_once('include/feed.php'); require_once('include/Contact.php'); require_once('mod/share.php'); require_once('include/enotify.php'); +require_once('include/import-dfrn.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); @@ -1693,6 +1694,13 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } return; } + // dfrn-test +// if ($contact['network'] === NETWORK_DFRN) { +// logger("Consume DFRN messages", LOGGER_DEBUG); +// logger("dfrn-test"); +// dfrn2::import($xml,$importer, $contact); +// return; +// } // Test - remove before flight //if ($pass < 2) { @@ -2398,6 +2406,8 @@ function item_is_remote_self($contact, &$datarray) { } function local_delivery($importer,$data) { + // dfrn-Test + return dfrn2::import($data, $importer, $contact); require_once('library/simplepie/simplepie.inc'); diff --git a/mod/ping.php b/mod/ping.php index 57728d3294..e517f785e8 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -389,7 +389,11 @@ function ping_get_notifications($uid) { // Replace the name with {0} but ensure to make that only once // The {0} is used later and prints the name in bold. - $pos = strpos($notification["message"],$notification['name']); + if ($notification['name'] != "") + $pos = strpos($notification["message"],$notification['name']); + else + $pos = false; + if ($pos !== false) $notification["message"] = substr_replace($notification["message"],"{0}",$pos,strlen($notification["name"])); From 4e513d3885eb04119c785f4c15561e6c32568078 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 30 Jan 2016 03:17:46 +0100 Subject: [PATCH 008/273] DFRN: Deletions should work now as well --- include/import-dfrn.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 20735bd507..b1a1c80e04 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -521,8 +521,8 @@ class dfrn2 { $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", intval($importer["uid"]), dbesc($item["uri"])); if ($r) { - //logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); - //return false; + logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); + return false; } // Is it a reply? @@ -694,7 +694,7 @@ class dfrn2 { else $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); - if (!$uri OR !$contact) + if (!$uri OR !$contact_id) return false; $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` From eb17fe7324507aa60d7227397cd98aa230aa3c26 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 30 Jan 2016 14:13:58 +0100 Subject: [PATCH 009/273] Some missing parts added --- include/import-dfrn.php | 252 ++++++++++++++++++++++++++++++++++++++-- include/items.php | 4 +- 2 files changed, 247 insertions(+), 9 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index b1a1c80e04..9660d9209c 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -520,10 +520,10 @@ class dfrn2 { $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", intval($importer["uid"]), dbesc($item["uri"])); - if ($r) { - logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); - return false; - } + //if ($r) { + // logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); + // return false; + //} // Is it a reply? $inreplyto = $xpath->query('thr:in-reply-to', $entry); @@ -652,8 +652,239 @@ class dfrn2 { } } - //print_r($item); - $item_id = item_store($item); +/* +// reply + // not allowed to post + + if($contact['rel'] == CONTACT_IS_FOLLOWER) + continue; + + $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), + intval($importer['uid']) + ); + + // Update content if 'updated' changes + + if(count($r)) { + if (edited_timestamp_is_newer($r[0], $datarray)) { + + // do not accept (ignore) an earlier edit than one we currently have. + if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) + continue; + + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = + '%s' WHERE `uri` = '%s' AND `uid` = %d", + dbesc($datarray['title']), + dbesc($datarray['body']), + dbesc($datarray['tag']), + dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), + dbesc(datetime_convert()), + dbesc($item_id), + intval($importer['uid']) + ); + create_tags_from_itemuri($item_id, $importer['uid']); + update_thread_uri($item_id, $importer['uid']); + } + + // update last-child if it changes + // update last-child if it changes + + $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); + if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) { + $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc(datetime_convert()), + dbesc($parent_uri), + intval($importer['uid']) + ); + $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + intval($allow[0]['data']), + dbesc(datetime_convert()), + dbesc($item_id), + intval($importer['uid']) + ); + update_thread_uri($item_id, $importer['uid']); + } + continue; + } + if(($datarray['verb'] === ACTIVITY_LIKE) + || ($datarray['verb'] === ACTIVITY_DISLIKE) + || ($datarray['verb'] === ACTIVITY_ATTEND) + || ($datarray['verb'] === ACTIVITY_ATTENDNO) + || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { + $datarray['type'] = 'activity'; + $datarray['gravity'] = GRAVITY_LIKE; + // only one like or dislike per person + // splitted into two queries for performance issues + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", + intval($datarray['uid']), + dbesc($datarray['author-link']), + dbesc($datarray['verb']), + dbesc($datarray['parent-uri']) + ); + if($r && count($r)) + continue; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", + intval($datarray['uid']), + dbesc($datarray['author-link']), + dbesc($datarray['verb']), + dbesc($datarray['parent-uri']) + ); + if($r && count($r)) + continue; + } + if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($datarray['object'],false); + $xt = parse_xml_string($datarray['target'],false); + + if($xt->type == ACTIVITY_OBJ_NOTE) { + $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", + dbesc($xt->id), + intval($importer['importer_uid']) + ); + if(! count($r)) + continue; + + // extract tag, if not duplicate, add to parent item + if($xo->id && $xo->content) { + $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; + if(! (stristr($r[0]['tag'],$newtag))) { + q("UPDATE item SET tag = '%s' WHERE id = %d", + dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), + intval($r[0]['id']) + ); + create_tags_from_item($r[0]['id']); + } + } + } + } + + + +// toplevel + // special handling for events + + if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { + $ev = bbtoevent($datarray['body']); + if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { + $ev['uid'] = $importer['uid']; + $ev['uri'] = $item_id; + $ev['edited'] = $datarray['edited']; + $ev['private'] = $datarray['private']; + $ev['guid'] = $datarray['guid']; + + if(is_array($contact)) + $ev['cid'] = $contact['id']; + $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), + intval($importer['uid']) + ); + if(count($r)) + $ev['id'] = $r[0]['id']; + $xyz = event_store($ev); + continue; + } + } + + + $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), + intval($importer['uid']) + ); + + // Update content if 'updated' changes + + if(count($r)) { + if (edited_timestamp_is_newer($r[0], $datarray)) { + + // do not accept (ignore) an earlier edit than one we currently have. + if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) + continue; + + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = + '%s' WHERE `uri` = '%s' AND `uid` = %d", + dbesc($datarray['title']), + dbesc($datarray['body']), + dbesc($datarray['tag']), + dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), + dbesc(datetime_convert()), + dbesc($item_id), + intval($importer['uid']) + ); + create_tags_from_itemuri($item_id, $importer['uid']); + update_thread_uri($item_id, $importer['uid']); + } + + // update last-child if it changes + + $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); + if($allow && $allow[0]['data'] != $r[0]['last-child']) { + $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + intval($allow[0]['data']), + dbesc(datetime_convert()), + dbesc($item_id), + intval($importer['uid']) + ); + update_thread_uri($item_id, $importer['uid']); + } + continue; + } + + + +toplevel: + + if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) { + logger('consume-feed: New follower'); + new_follower($importer,$contact,$datarray,$item); + return; + } + if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) { + lose_follower($importer,$contact,$datarray,$item); + return; + } + + if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) { + logger('consume-feed: New friend request'); + new_follower($importer,$contact,$datarray,$item,true); + return; + } + if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) { + lose_sharer($importer,$contact,$datarray,$item); + return; + } + + + if(! is_array($contact)) + return; + + if(! link_compare($datarray['owner-link'],$contact['url'])) { + // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, + // but otherwise there's a possible data mixup on the sender's system. + // the tgroup delivery code called from item_store will correct it if it's a forum, + // but we're going to unconditionally correct it here so that the post will always be owned by our contact. + logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); + $datarray['owner-name'] = $contact['name']; + $datarray['owner-link'] = $contact['url']; + $datarray['owner-avatar'] = $contact['thumb']; + } + + // We've allowed "followers" to reach this point so we can decide if they are + // posting an @-tag delivery, which followers are allowed to do for certain + // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it. + + if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray))) + continue; + + // This is my contact on another system, but it's really me. + // Turn this into a wall post. + $notify = item_is_remote_self($contact, $datarray); + +*/ + print_r($item); + return; + //$item_id = item_store($item); if (!$item_id) { logger("Error storing item", LOGGER_DEBUG); @@ -703,7 +934,9 @@ class dfrn2 { intval($importer["uid"]), intval($contact_id) ); - if(count($r)) { + if(!count($r)) + logger("Item with uri ".$uri." from contact ".$contact_id." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG); + else { $item = $r[0]; if(!$item["deleted"]) @@ -792,6 +1025,11 @@ class dfrn2 { ); } } + // if this is a relayed delete, propagate it to other recipients + +// if($is_a_remote_delete) + // proc_run('php',"include/notifier.php","drop",$item['id']); + } } } diff --git a/include/items.php b/include/items.php index 7df4e0c78a..6ed53ffcd8 100644 --- a/include/items.php +++ b/include/items.php @@ -17,7 +17,7 @@ require_once('include/feed.php'); require_once('include/Contact.php'); require_once('mod/share.php'); require_once('include/enotify.php'); -require_once('include/import-dfrn.php'); +//require_once('include/import-dfrn.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); @@ -2407,7 +2407,7 @@ function item_is_remote_self($contact, &$datarray) { function local_delivery($importer,$data) { // dfrn-Test - return dfrn2::import($data, $importer, $contact); + //return dfrn2::import($data, $importer, $contact); require_once('library/simplepie/simplepie.inc'); From 3ea5706d167df8e576bbe6ced0a4caa836f644e0 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 30 Jan 2016 16:37:18 +0100 Subject: [PATCH 010/273] Resolved namespace trouble --- include/dfrn.php | 2 +- include/import-dfrn.php | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 50d78de3c4..4c1f21dd06 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -383,7 +383,7 @@ class dfrn { if ($alternatelink == "") $alternatelink = $owner['url']; - $root = $doc->createElementNS(NS_ATOM, 'feed'); + $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); $doc->appendChild($root); $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 9660d9209c..8a72e40060 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -18,15 +18,6 @@ require_once("include/items.php"); require_once("include/tags.php"); require_once("include/files.php"); -define("NS_ATOM", "http://www.w3.org/2005/Atom"); -define("NS_THR", "http://purl.org/syndication/thread/1.0"); -define("NS_GEORSS", "http://www.georss.org/georss"); -define("NS_ACTIVITY", "http://activitystrea.ms/spec/1.0/"); -define("NS_MEDIA", "http://purl.org/syndication/atommedia"); -define("NS_POCO", "http://portablecontacts.net/spec/1.0"); -define("NS_OSTATUS", "http://ostatus.org/schema/1.0"); -define("NS_STATUSNET", "http://status.net/schema/api/1/"); - class dfrn2 { /** * @brief Add new birthday event for this person @@ -236,7 +227,7 @@ class dfrn2 { $obj_doc = new DOMDocument('1.0', 'utf-8'); $obj_doc->formatOutput = true; - $obj_element = $obj_doc->createElementNS(NS_ATOM, $element); + $obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element); $activity_type = $xpath->query('activity:object-type/text()', $activity)->item(0)->nodeValue; xml_add_element($obj_doc, $obj_element, "type", $activity_type); From 447ed7af53d25b080da630283757876b17600ded Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Sun, 31 Jan 2016 12:02:05 +0100 Subject: [PATCH 011/273] IT update to the strings --- view/it/messages.po | 2205 +++++++++++++++++++++---------------------- view/it/strings.php | 77 +- 2 files changed, 1120 insertions(+), 1162 deletions(-) diff --git a/view/it/messages.po b/view/it/messages.po index 5f0129fb0a..b2b88bc72f 100644 --- a/view/it/messages.po +++ b/view/it/messages.po @@ -9,15 +9,15 @@ # fabrixxm <fabrix.xm@gmail.com>, 2011-2012 # Francesco Apruzzese <cescoap@gmail.com>, 2012-2013 # ufic <marco@carnazzo.it>, 2012 -# tuscanhobbit <pynolo@tarine.net>, 2012 -# Sandro Santilli <strk@keybit.net>, 2015 +# Paolo Wave <pynolo@tarine.net>, 2012 +# Sandro Santilli <strk@keybit.net>, 2015-2016 msgid "" msgstr "" "Project-Id-Version: friendica\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-12-14 07:48+0100\n" -"PO-Revision-Date: 2015-12-14 13:05+0000\n" -"Last-Translator: fabrixxm <fabrix.xm@gmail.com>\n" +"POT-Creation-Date: 2016-01-24 06:49+0100\n" +"PO-Revision-Date: 2016-01-30 08:43+0000\n" +"Last-Translator: Sandro Santilli <strk@keybit.net>\n" "Language-Team: Italian (http://www.transifex.com/Friendica/friendica/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -25,26 +25,26 @@ msgstr "" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mod/contacts.php:50 include/identity.php:380 +#: mod/contacts.php:50 include/identity.php:395 msgid "Network:" msgstr "Rete:" -#: mod/contacts.php:51 mod/contacts.php:986 mod/videos.php:37 -#: mod/viewcontacts.php:105 mod/dirfind.php:208 mod/network.php:596 -#: mod/allfriends.php:72 mod/match.php:82 mod/directory.php:172 -#: mod/common.php:124 mod/suggest.php:95 mod/photos.php:41 -#: include/identity.php:295 +#: mod/contacts.php:51 mod/contacts.php:961 mod/videos.php:37 +#: mod/viewcontacts.php:105 mod/dirfind.php:214 mod/network.php:598 +#: mod/allfriends.php:77 mod/match.php:82 mod/directory.php:172 +#: mod/common.php:123 mod/suggest.php:95 mod/photos.php:41 +#: include/identity.php:298 msgid "Forum" msgstr "Forum" #: mod/contacts.php:128 #, php-format msgid "%d contact edited." -msgid_plural "%d contacts edited" -msgstr[0] "%d contatto modificato" -msgstr[1] "%d contatti modificati" +msgid_plural "%d contacts edited." +msgstr[0] "" +msgstr[1] "" -#: mod/contacts.php:159 mod/contacts.php:382 +#: mod/contacts.php:159 mod/contacts.php:383 msgid "Could not access contact record." msgstr "Non è possibile accedere al contatto." @@ -56,15 +56,15 @@ msgstr "Non riesco a trovare il profilo selezionato." msgid "Contact updated." msgstr "Contatto aggiornato." -#: mod/contacts.php:208 mod/dfrn_request.php:578 +#: mod/contacts.php:208 mod/dfrn_request.php:575 msgid "Failed to update contact record." msgstr "Errore nell'aggiornamento del contatto." -#: mod/contacts.php:364 mod/manage.php:96 mod/display.php:496 +#: mod/contacts.php:365 mod/manage.php:96 mod/display.php:509 #: mod/profile_photo.php:19 mod/profile_photo.php:175 #: mod/profile_photo.php:186 mod/profile_photo.php:199 -#: mod/ostatus_subscribe.php:9 mod/follow.php:10 mod/follow.php:72 -#: mod/follow.php:137 mod/item.php:169 mod/item.php:185 mod/group.php:19 +#: mod/ostatus_subscribe.php:9 mod/follow.php:11 mod/follow.php:73 +#: mod/follow.php:155 mod/item.php:180 mod/item.php:192 mod/group.php:19 #: mod/dfrn_confirm.php:55 mod/fsuggest.php:78 mod/wall_upload.php:77 #: mod/wall_upload.php:80 mod/viewcontacts.php:40 mod/notifications.php:69 #: mod/message.php:45 mod/message.php:181 mod/crepair.php:117 @@ -72,129 +72,129 @@ msgstr "Errore nell'aggiornamento del contatto." #: mod/allfriends.php:12 mod/events.php:165 mod/wallmessage.php:9 #: mod/wallmessage.php:33 mod/wallmessage.php:79 mod/wallmessage.php:103 #: mod/wall_attach.php:67 mod/wall_attach.php:70 mod/settings.php:20 -#: mod/settings.php:116 mod/settings.php:637 mod/register.php:42 +#: mod/settings.php:126 mod/settings.php:646 mod/register.php:42 #: mod/delegate.php:12 mod/common.php:18 mod/mood.php:114 mod/suggest.php:58 #: mod/profiles.php:165 mod/profiles.php:615 mod/editpost.php:10 #: mod/api.php:26 mod/api.php:31 mod/notes.php:22 mod/poke.php:149 #: mod/repair_ostatus.php:9 mod/invite.php:15 mod/invite.php:101 #: mod/photos.php:171 mod/photos.php:1105 mod/regmod.php:110 -#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5067 index.php:382 +#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5096 index.php:383 msgid "Permission denied." msgstr "Permesso negato." -#: mod/contacts.php:403 +#: mod/contacts.php:404 msgid "Contact has been blocked" msgstr "Il contatto è stato bloccato" -#: mod/contacts.php:403 +#: mod/contacts.php:404 msgid "Contact has been unblocked" msgstr "Il contatto è stato sbloccato" -#: mod/contacts.php:414 +#: mod/contacts.php:415 msgid "Contact has been ignored" msgstr "Il contatto è ignorato" -#: mod/contacts.php:414 +#: mod/contacts.php:415 msgid "Contact has been unignored" msgstr "Il contatto non è più ignorato" -#: mod/contacts.php:426 +#: mod/contacts.php:427 msgid "Contact has been archived" msgstr "Il contatto è stato archiviato" -#: mod/contacts.php:426 +#: mod/contacts.php:427 msgid "Contact has been unarchived" msgstr "Il contatto è stato dearchiviato" -#: mod/contacts.php:453 mod/contacts.php:801 +#: mod/contacts.php:454 mod/contacts.php:802 msgid "Do you really want to delete this contact?" msgstr "Vuoi veramente cancellare questo contatto?" -#: mod/contacts.php:455 mod/follow.php:105 mod/message.php:216 -#: mod/settings.php:1094 mod/settings.php:1100 mod/settings.php:1108 -#: mod/settings.php:1112 mod/settings.php:1117 mod/settings.php:1123 -#: mod/settings.php:1129 mod/settings.php:1135 mod/settings.php:1161 -#: mod/settings.php:1162 mod/settings.php:1163 mod/settings.php:1164 -#: mod/settings.php:1165 mod/dfrn_request.php:850 mod/register.php:238 +#: mod/contacts.php:456 mod/follow.php:110 mod/message.php:216 +#: mod/settings.php:1103 mod/settings.php:1109 mod/settings.php:1117 +#: mod/settings.php:1121 mod/settings.php:1126 mod/settings.php:1132 +#: mod/settings.php:1138 mod/settings.php:1144 mod/settings.php:1170 +#: mod/settings.php:1171 mod/settings.php:1172 mod/settings.php:1173 +#: mod/settings.php:1174 mod/dfrn_request.php:857 mod/register.php:238 #: mod/suggest.php:29 mod/profiles.php:658 mod/profiles.php:661 -#: mod/profiles.php:687 mod/api.php:105 include/items.php:4899 +#: mod/profiles.php:687 mod/api.php:105 include/items.php:4928 msgid "Yes" msgstr "Si" -#: mod/contacts.php:458 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:116 +#: mod/contacts.php:459 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121 #: mod/videos.php:131 mod/message.php:219 mod/fbrowser.php:93 -#: mod/fbrowser.php:128 mod/settings.php:651 mod/settings.php:677 -#: mod/dfrn_request.php:864 mod/suggest.php:32 mod/editpost.php:148 -#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1221 -#: include/items.php:4902 +#: mod/fbrowser.php:128 mod/settings.php:660 mod/settings.php:686 +#: mod/dfrn_request.php:871 mod/suggest.php:32 mod/editpost.php:148 +#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1220 +#: include/items.php:4931 msgid "Cancel" msgstr "Annulla" -#: mod/contacts.php:470 +#: mod/contacts.php:471 msgid "Contact has been removed." msgstr "Il contatto è stato rimosso." -#: mod/contacts.php:511 +#: mod/contacts.php:512 #, php-format msgid "You are mutual friends with %s" msgstr "Sei amico reciproco con %s" -#: mod/contacts.php:515 +#: mod/contacts.php:516 #, php-format msgid "You are sharing with %s" msgstr "Stai condividendo con %s" -#: mod/contacts.php:520 +#: mod/contacts.php:521 #, php-format msgid "%s is sharing with you" msgstr "%s sta condividendo con te" -#: mod/contacts.php:540 +#: mod/contacts.php:541 msgid "Private communications are not available for this contact." msgstr "Le comunicazioni private non sono disponibili per questo contatto." -#: mod/contacts.php:543 mod/admin.php:645 +#: mod/contacts.php:544 mod/admin.php:822 msgid "Never" msgstr "Mai" -#: mod/contacts.php:547 +#: mod/contacts.php:548 msgid "(Update was successful)" msgstr "(L'aggiornamento è stato completato)" -#: mod/contacts.php:547 +#: mod/contacts.php:548 msgid "(Update was not successful)" msgstr "(L'aggiornamento non è stato completato)" -#: mod/contacts.php:549 +#: mod/contacts.php:550 msgid "Suggest friends" msgstr "Suggerisci amici" -#: mod/contacts.php:553 +#: mod/contacts.php:554 #, php-format msgid "Network type: %s" msgstr "Tipo di rete: %s" -#: mod/contacts.php:566 +#: mod/contacts.php:567 msgid "Communications lost with this contact!" msgstr "Comunicazione con questo contatto persa!" -#: mod/contacts.php:569 +#: mod/contacts.php:570 msgid "Fetch further information for feeds" msgstr "Recupera maggiori infomazioni per i feed" -#: mod/contacts.php:570 mod/admin.php:654 +#: mod/contacts.php:571 mod/admin.php:831 msgid "Disabled" msgstr "Disabilitato" -#: mod/contacts.php:570 +#: mod/contacts.php:571 msgid "Fetch information" msgstr "Recupera informazioni" -#: mod/contacts.php:570 +#: mod/contacts.php:571 msgid "Fetch information and keywords" msgstr "Recupera informazioni e parole chiave" -#: mod/contacts.php:586 mod/manage.php:143 mod/fsuggest.php:107 +#: mod/contacts.php:587 mod/manage.php:143 mod/fsuggest.php:107 #: mod/message.php:342 mod/message.php:525 mod/crepair.php:196 #: mod/events.php:574 mod/content.php:712 mod/install.php:261 #: mod/install.php:299 mod/mood.php:137 mod/profiles.php:696 @@ -204,308 +204,308 @@ msgstr "Recupera informazioni e parole chiave" #: object/Item.php:710 view/theme/cleanzero/config.php:80 #: view/theme/dispy/config.php:70 view/theme/quattro/config.php:64 #: view/theme/diabook/config.php:148 view/theme/diabook/theme.php:633 -#: view/theme/clean/config.php:83 view/theme/vier/config.php:107 -#: view/theme/duepuntozero/config.php:59 +#: view/theme/vier/config.php:107 view/theme/duepuntozero/config.php:59 msgid "Submit" msgstr "Invia" -#: mod/contacts.php:587 +#: mod/contacts.php:588 msgid "Profile Visibility" msgstr "Visibilità del profilo" -#: mod/contacts.php:588 +#: mod/contacts.php:589 #, php-format msgid "" "Please choose the profile you would like to display to %s when viewing your " "profile securely." msgstr "Seleziona il profilo che vuoi mostrare a %s quando visita il tuo profilo in modo sicuro." -#: mod/contacts.php:589 +#: mod/contacts.php:590 msgid "Contact Information / Notes" msgstr "Informazioni / Note sul contatto" -#: mod/contacts.php:590 +#: mod/contacts.php:591 msgid "Edit contact notes" msgstr "Modifica note contatto" -#: mod/contacts.php:595 mod/contacts.php:977 mod/viewcontacts.php:97 +#: mod/contacts.php:596 mod/contacts.php:952 mod/viewcontacts.php:97 #: mod/nogroup.php:41 #, php-format msgid "Visit %s's profile [%s]" msgstr "Visita il profilo di %s [%s]" -#: mod/contacts.php:596 +#: mod/contacts.php:597 msgid "Block/Unblock contact" msgstr "Blocca/Sblocca contatto" -#: mod/contacts.php:597 +#: mod/contacts.php:598 msgid "Ignore contact" msgstr "Ignora il contatto" -#: mod/contacts.php:598 +#: mod/contacts.php:599 msgid "Repair URL settings" msgstr "Impostazioni riparazione URL" -#: mod/contacts.php:599 +#: mod/contacts.php:600 msgid "View conversations" msgstr "Vedi conversazioni" -#: mod/contacts.php:601 +#: mod/contacts.php:602 msgid "Delete contact" msgstr "Rimuovi contatto" -#: mod/contacts.php:605 +#: mod/contacts.php:606 msgid "Last update:" msgstr "Ultimo aggiornamento:" -#: mod/contacts.php:607 +#: mod/contacts.php:608 msgid "Update public posts" msgstr "Aggiorna messaggi pubblici" -#: mod/contacts.php:609 mod/admin.php:1653 +#: mod/contacts.php:610 msgid "Update now" msgstr "Aggiorna adesso" -#: mod/contacts.php:611 mod/dirfind.php:190 mod/allfriends.php:60 -#: mod/match.php:71 mod/suggest.php:82 include/contact_widgets.php:32 -#: include/Contact.php:321 include/conversation.php:924 +#: mod/contacts.php:612 mod/follow.php:103 mod/dirfind.php:196 +#: mod/allfriends.php:65 mod/match.php:71 mod/suggest.php:82 +#: include/contact_widgets.php:32 include/Contact.php:297 +#: include/conversation.php:924 msgid "Connect/Follow" msgstr "Connetti/segui" -#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864 -#: mod/admin.php:1117 +#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865 +#: mod/admin.php:1312 msgid "Unblock" msgstr "Sblocca" -#: mod/contacts.php:614 mod/contacts.php:805 mod/contacts.php:864 -#: mod/admin.php:1116 +#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865 +#: mod/admin.php:1311 msgid "Block" msgstr "Blocca" -#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871 +#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872 msgid "Unignore" msgstr "Non ignorare" -#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:871 +#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872 #: mod/notifications.php:54 mod/notifications.php:179 #: mod/notifications.php:259 msgid "Ignore" msgstr "Ignora" -#: mod/contacts.php:618 +#: mod/contacts.php:619 msgid "Currently blocked" msgstr "Bloccato" -#: mod/contacts.php:619 +#: mod/contacts.php:620 msgid "Currently ignored" msgstr "Ignorato" -#: mod/contacts.php:620 +#: mod/contacts.php:621 msgid "Currently archived" msgstr "Al momento archiviato" -#: mod/contacts.php:621 mod/notifications.php:172 mod/notifications.php:251 +#: mod/contacts.php:622 mod/notifications.php:172 mod/notifications.php:251 msgid "Hide this contact from others" msgstr "Nascondi questo contatto agli altri" -#: mod/contacts.php:621 +#: mod/contacts.php:622 msgid "" "Replies/likes to your public posts <strong>may</strong> still be visible" msgstr "Risposte ai tuoi post pubblici <strong>possono</strong> essere comunque visibili" -#: mod/contacts.php:622 +#: mod/contacts.php:623 msgid "Notification for new posts" msgstr "Notifica per i nuovi messaggi" -#: mod/contacts.php:622 +#: mod/contacts.php:623 msgid "Send a notification of every new post of this contact" msgstr "Invia una notifica per ogni nuovo messaggio di questo contatto" -#: mod/contacts.php:625 +#: mod/contacts.php:626 msgid "Blacklisted keywords" msgstr "Parole chiave in blacklist" -#: mod/contacts.php:625 +#: mod/contacts.php:626 msgid "" "Comma separated list of keywords that should not be converted to hashtags, " "when \"Fetch information and keywords\" is selected" msgstr "Lista separata da virgola di parole chiave che non dovranno essere convertite in hastag, quando \"Recupera informazioni e parole chiave\" è selezionato" -#: mod/contacts.php:632 mod/follow.php:121 mod/notifications.php:255 +#: mod/contacts.php:633 mod/follow.php:126 mod/notifications.php:255 msgid "Profile URL" msgstr "URL Profilo" -#: mod/contacts.php:635 mod/follow.php:125 mod/notifications.php:244 -#: mod/events.php:566 mod/directory.php:145 include/identity.php:304 -#: include/bb2diaspora.php:170 include/event.php:36 include/event.php:60 +#: mod/contacts.php:636 mod/notifications.php:244 mod/events.php:566 +#: mod/directory.php:145 include/identity.php:308 include/bb2diaspora.php:170 +#: include/event.php:36 include/event.php:60 msgid "Location:" msgstr "Posizione:" -#: mod/contacts.php:637 mod/follow.php:127 mod/notifications.php:246 -#: mod/directory.php:153 include/identity.php:313 include/identity.php:621 +#: mod/contacts.php:638 mod/notifications.php:246 mod/directory.php:153 +#: include/identity.php:317 include/identity.php:631 msgid "About:" msgstr "Informazioni:" -#: mod/contacts.php:639 mod/follow.php:129 mod/notifications.php:248 -#: include/identity.php:615 +#: mod/contacts.php:640 mod/follow.php:134 mod/notifications.php:248 +#: include/identity.php:625 msgid "Tags:" msgstr "Tag:" -#: mod/contacts.php:684 +#: mod/contacts.php:685 msgid "Suggestions" msgstr "Suggerimenti" -#: mod/contacts.php:687 +#: mod/contacts.php:688 msgid "Suggest potential friends" msgstr "Suggerisci potenziali amici" -#: mod/contacts.php:692 mod/group.php:192 +#: mod/contacts.php:693 mod/group.php:192 msgid "All Contacts" msgstr "Tutti i contatti" -#: mod/contacts.php:695 +#: mod/contacts.php:696 msgid "Show all contacts" msgstr "Mostra tutti i contatti" -#: mod/contacts.php:700 +#: mod/contacts.php:701 msgid "Unblocked" msgstr "Sbloccato" -#: mod/contacts.php:703 +#: mod/contacts.php:704 msgid "Only show unblocked contacts" msgstr "Mostra solo contatti non bloccati" -#: mod/contacts.php:709 +#: mod/contacts.php:710 msgid "Blocked" msgstr "Bloccato" -#: mod/contacts.php:712 +#: mod/contacts.php:713 msgid "Only show blocked contacts" msgstr "Mostra solo contatti bloccati" -#: mod/contacts.php:718 +#: mod/contacts.php:719 msgid "Ignored" msgstr "Ignorato" -#: mod/contacts.php:721 +#: mod/contacts.php:722 msgid "Only show ignored contacts" msgstr "Mostra solo contatti ignorati" -#: mod/contacts.php:727 +#: mod/contacts.php:728 msgid "Archived" msgstr "Achiviato" -#: mod/contacts.php:730 +#: mod/contacts.php:731 msgid "Only show archived contacts" msgstr "Mostra solo contatti archiviati" -#: mod/contacts.php:736 +#: mod/contacts.php:737 msgid "Hidden" msgstr "Nascosto" -#: mod/contacts.php:739 +#: mod/contacts.php:740 msgid "Only show hidden contacts" msgstr "Mostra solo contatti nascosti" -#: mod/contacts.php:792 mod/contacts.php:840 mod/viewcontacts.php:116 -#: include/identity.php:732 include/identity.php:735 include/text.php:1012 +#: mod/contacts.php:793 mod/contacts.php:841 mod/viewcontacts.php:116 +#: include/identity.php:741 include/identity.php:744 include/text.php:1012 #: include/nav.php:123 include/nav.php:187 view/theme/diabook/theme.php:125 msgid "Contacts" msgstr "Contatti" -#: mod/contacts.php:796 +#: mod/contacts.php:797 msgid "Search your contacts" msgstr "Cerca nei tuoi contatti" -#: mod/contacts.php:797 +#: mod/contacts.php:798 msgid "Finding: " msgstr "Ricerca: " -#: mod/contacts.php:798 mod/directory.php:210 include/contact_widgets.php:34 +#: mod/contacts.php:799 mod/directory.php:210 include/contact_widgets.php:34 msgid "Find" msgstr "Trova" -#: mod/contacts.php:804 mod/settings.php:146 mod/settings.php:676 +#: mod/contacts.php:805 mod/settings.php:156 mod/settings.php:685 msgid "Update" msgstr "Aggiorna" -#: mod/contacts.php:807 mod/contacts.php:878 +#: mod/contacts.php:808 mod/contacts.php:879 msgid "Archive" msgstr "Archivia" -#: mod/contacts.php:807 mod/contacts.php:878 +#: mod/contacts.php:808 mod/contacts.php:879 msgid "Unarchive" msgstr "Dearchivia" -#: mod/contacts.php:808 mod/group.php:171 mod/admin.php:1115 -#: mod/content.php:440 mod/content.php:743 mod/settings.php:713 +#: mod/contacts.php:809 mod/group.php:171 mod/admin.php:1310 +#: mod/content.php:440 mod/content.php:743 mod/settings.php:722 #: mod/photos.php:1723 object/Item.php:134 include/conversation.php:635 msgid "Delete" msgstr "Rimuovi" -#: mod/contacts.php:821 include/identity.php:677 include/nav.php:75 +#: mod/contacts.php:822 include/identity.php:686 include/nav.php:75 msgid "Status" msgstr "Stato" -#: mod/contacts.php:824 include/identity.php:680 +#: mod/contacts.php:825 mod/follow.php:143 include/identity.php:689 msgid "Status Messages and Posts" msgstr "Messaggi di stato e post" -#: mod/contacts.php:829 mod/profperm.php:104 mod/newmember.php:32 -#: include/identity.php:569 include/identity.php:655 include/identity.php:685 +#: mod/contacts.php:830 mod/profperm.php:104 mod/newmember.php:32 +#: include/identity.php:579 include/identity.php:665 include/identity.php:694 #: include/nav.php:76 view/theme/diabook/theme.php:124 msgid "Profile" msgstr "Profilo" -#: mod/contacts.php:832 include/identity.php:688 +#: mod/contacts.php:833 include/identity.php:697 msgid "Profile Details" msgstr "Dettagli del profilo" -#: mod/contacts.php:843 +#: mod/contacts.php:844 msgid "View all contacts" msgstr "Vedi tutti i contatti" -#: mod/contacts.php:849 mod/common.php:135 +#: mod/contacts.php:850 mod/common.php:134 msgid "Common Friends" msgstr "Amici in comune" -#: mod/contacts.php:852 +#: mod/contacts.php:853 msgid "View all common friends" msgstr "Vedi tutti gli amici in comune" -#: mod/contacts.php:856 +#: mod/contacts.php:857 msgid "Repair" msgstr "Ripara" -#: mod/contacts.php:859 +#: mod/contacts.php:860 msgid "Advanced Contact Settings" msgstr "Impostazioni avanzate Contatto" -#: mod/contacts.php:867 +#: mod/contacts.php:868 msgid "Toggle Blocked status" msgstr "Inverti stato \"Blocca\"" -#: mod/contacts.php:874 +#: mod/contacts.php:875 msgid "Toggle Ignored status" msgstr "Inverti stato \"Ignora\"" -#: mod/contacts.php:881 +#: mod/contacts.php:882 msgid "Toggle Archive status" msgstr "Inverti stato \"Archiviato\"" -#: mod/contacts.php:949 +#: mod/contacts.php:924 msgid "Mutual Friendship" msgstr "Amicizia reciproca" -#: mod/contacts.php:953 +#: mod/contacts.php:928 msgid "is a fan of yours" msgstr "è un tuo fan" -#: mod/contacts.php:957 +#: mod/contacts.php:932 msgid "you are a fan of" msgstr "sei un fan di" -#: mod/contacts.php:978 mod/nogroup.php:42 +#: mod/contacts.php:953 mod/nogroup.php:42 msgid "Edit contact" msgstr "Modifca contatto" @@ -531,7 +531,7 @@ msgstr "Seleziona un'identità da gestire:" msgid "Post successful." msgstr "Inviato!" -#: mod/profperm.php:19 mod/group.php:72 index.php:381 +#: mod/profperm.php:19 mod/group.php:72 index.php:382 msgid "Permission denied" msgstr "Permesso negato" @@ -555,23 +555,23 @@ msgstr "Visibile a" msgid "All Contacts (with secure profile access)" msgstr "Tutti i contatti (con profilo ad accesso sicuro)" -#: mod/display.php:82 mod/display.php:283 mod/display.php:500 -#: mod/viewsrc.php:15 mod/admin.php:196 mod/admin.php:1160 mod/admin.php:1381 -#: mod/notice.php:15 include/items.php:4858 +#: mod/display.php:82 mod/display.php:291 mod/display.php:513 +#: mod/viewsrc.php:15 mod/admin.php:234 mod/admin.php:1365 mod/admin.php:1599 +#: mod/notice.php:15 include/items.php:4887 msgid "Item not found." msgstr "Elemento non trovato." -#: mod/display.php:211 mod/videos.php:197 mod/viewcontacts.php:35 -#: mod/community.php:18 mod/dfrn_request.php:779 mod/search.php:93 +#: mod/display.php:220 mod/videos.php:197 mod/viewcontacts.php:35 +#: mod/community.php:22 mod/dfrn_request.php:786 mod/search.php:93 #: mod/search.php:99 mod/directory.php:37 mod/photos.php:976 msgid "Public access denied." msgstr "Accesso negato." -#: mod/display.php:331 mod/profile.php:155 +#: mod/display.php:339 mod/profile.php:155 msgid "Access to this profile has been restricted." msgstr "L'accesso a questo profilo è stato limitato." -#: mod/display.php:493 +#: mod/display.php:506 msgid "Item has been removed." msgstr "L'oggetto è stato rimosso." @@ -606,8 +606,8 @@ msgid "" " join." msgstr "Sulla tua pagina <em>Quick Start</em> - veloce introduzione alla tua pagina profilo e alla pagina Rete, fai qualche nuova amicizia, e trova qualche gruppo a cui unirti." -#: mod/newmember.php:22 mod/admin.php:1212 mod/admin.php:1457 -#: mod/settings.php:99 include/nav.php:182 view/theme/diabook/theme.php:544 +#: mod/newmember.php:22 mod/admin.php:1418 mod/admin.php:1676 +#: mod/settings.php:109 include/nav.php:182 view/theme/diabook/theme.php:544 #: view/theme/diabook/theme.php:648 msgid "Settings" msgstr "Impostazioni" @@ -668,60 +668,44 @@ msgstr "Inserisci qualche parola chiave pubblica nel tuo profilo predefinito che msgid "Connecting" msgstr "Collegarsi" -#: mod/newmember.php:49 mod/newmember.php:51 include/contact_selectors.php:81 -msgid "Facebook" -msgstr "Facebook" - -#: mod/newmember.php:49 -msgid "" -"Authorise the Facebook Connector if you currently have a Facebook account " -"and we will (optionally) import all your Facebook friends and conversations." -msgstr "Autorizza il Facebook Connector se hai un account Facebook, e noi (opzionalmente) importeremo tuti i tuoi amici e le tue conversazioni da Facebook." - #: mod/newmember.php:51 -msgid "" -"<em>If</em> this is your own personal server, installing the Facebook addon " -"may ease your transition to the free social web." -msgstr "<em>Se</em questo è il tuo server personale, installare il plugin per Facebook puo' aiutarti nella transizione verso il web sociale libero." - -#: mod/newmember.php:56 msgid "Importing Emails" msgstr "Importare le Email" -#: mod/newmember.php:56 +#: mod/newmember.php:51 msgid "" "Enter your email access information on your Connector Settings page if you " "wish to import and interact with friends or mailing lists from your email " "INBOX" msgstr "Inserisci i tuoi dati di accesso all'email nella tua pagina Impostazioni Connettori se vuoi importare e interagire con amici o mailing list dalla tua casella di posta in arrivo" -#: mod/newmember.php:58 +#: mod/newmember.php:53 msgid "Go to Your Contacts Page" msgstr "Vai alla tua pagina Contatti" -#: mod/newmember.php:58 +#: mod/newmember.php:53 msgid "" "Your Contacts page is your gateway to managing friendships and connecting " "with friends on other networks. Typically you enter their address or site " "URL in the <em>Add New Contact</em> dialog." msgstr "La tua pagina Contatti è il mezzo per gestire le amicizie e collegarsi con amici su altre reti. Di solito, basta inserire l'indirizzo nel campo <em>Aggiungi Nuovo Contatto</em>" -#: mod/newmember.php:60 +#: mod/newmember.php:55 msgid "Go to Your Site's Directory" msgstr "Vai all'Elenco del tuo sito" -#: mod/newmember.php:60 +#: mod/newmember.php:55 msgid "" "The Directory page lets you find other people in this network or other " "federated sites. Look for a <em>Connect</em> or <em>Follow</em> link on " "their profile page. Provide your own Identity Address if requested." msgstr "La pagina Elenco ti permette di trovare altre persone in questa rete o in altri siti. Cerca un link <em>Connetti</em> o <em>Segui</em> nella loro pagina del profilo. Inserisci il tuo Indirizzo Identità, se richiesto." -#: mod/newmember.php:62 +#: mod/newmember.php:57 msgid "Finding New People" msgstr "Trova nuove persone" -#: mod/newmember.php:62 +#: mod/newmember.php:57 msgid "" "On the side panel of the Contacts page are several tools to find new " "friends. We can match people by interest, look up people by name or " @@ -730,41 +714,41 @@ msgid "" "hours." msgstr "Nel pannello laterale nella pagina \"Contatti\", ci sono diversi strumenti per trovare nuovi amici. Possiamo confrontare le persone per interessi, cercare le persone per nome e fornire suggerimenti basati sui tuoi contatti esistenti. Su un sito nuovo, i suggerimenti sono di solito presenti dopo 24 ore." -#: mod/newmember.php:66 include/group.php:283 +#: mod/newmember.php:61 include/group.php:283 msgid "Groups" msgstr "Gruppi" -#: mod/newmember.php:70 +#: mod/newmember.php:65 msgid "Group Your Contacts" msgstr "Raggruppa i tuoi contatti" -#: mod/newmember.php:70 +#: mod/newmember.php:65 msgid "" "Once you have made some friends, organize them into private conversation " "groups from the sidebar of your Contacts page and then you can interact with" " each group privately on your Network page." msgstr "Quando avrai alcuni amici, organizzali in gruppi di conversazioni private dalla barra laterale della tua pagina Contatti. Potrai interagire privatamente con ogni gruppo nella tua pagina Rete" -#: mod/newmember.php:73 +#: mod/newmember.php:68 msgid "Why Aren't My Posts Public?" msgstr "Perchè i miei post non sono pubblici?" -#: mod/newmember.php:73 +#: mod/newmember.php:68 msgid "" "Friendica respects your privacy. By default, your posts will only show up to" " people you've added as friends. For more information, see the help section " "from the link above." msgstr "Friendica rispetta la tua provacy. Per impostazione predefinita, i tuoi post sono mostrati solo alle persone che hai aggiunto come amici. Per maggiori informazioni guarda la sezione della guida dal link qui sopra." -#: mod/newmember.php:78 +#: mod/newmember.php:73 msgid "Getting Help" msgstr "Ottenere Aiuto" -#: mod/newmember.php:82 +#: mod/newmember.php:77 msgid "Go to the Help Section" msgstr "Vai alla sezione Guida" -#: mod/newmember.php:82 +#: mod/newmember.php:77 msgid "" "Our <strong>help</strong> pages may be consulted for detail on other program" " features and resources." @@ -779,7 +763,7 @@ msgid "" "Account not found and OpenID registration is not permitted on this site." msgstr "L'account non è stato trovato, e la registrazione via OpenID non è permessa su questo sito." -#: mod/openid.php:93 include/auth.php:112 include/auth.php:175 +#: mod/openid.php:93 include/auth.php:118 include/auth.php:181 msgid "Login failed." msgstr "Accesso fallito." @@ -865,18 +849,18 @@ msgstr "Immagine caricata con successo." msgid "Image upload failed." msgstr "Caricamento immagine fallito." -#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168 +#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165 #: include/conversation.php:130 include/conversation.php:266 -#: include/text.php:1993 include/diaspora.php:2146 +#: include/text.php:2000 include/diaspora.php:2169 #: view/theme/diabook/theme.php:471 msgid "photo" msgstr "foto" -#: mod/subthread.php:87 mod/tagger.php:62 mod/like.php:168 mod/like.php:346 -#: include/conversation.php:125 include/conversation.php:134 -#: include/conversation.php:261 include/conversation.php:270 -#: include/diaspora.php:2146 view/theme/diabook/theme.php:466 -#: view/theme/diabook/theme.php:475 +#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165 +#: include/like.php:334 include/conversation.php:125 +#: include/conversation.php:134 include/conversation.php:261 +#: include/conversation.php:270 include/diaspora.php:2169 +#: view/theme/diabook/theme.php:466 view/theme/diabook/theme.php:475 msgid "status" msgstr "stato" @@ -902,8 +886,8 @@ msgid "Remove" msgstr "Rimuovi" #: mod/ostatus_subscribe.php:14 -msgid "Subsribing to OStatus contacts" -msgstr "Iscrizione a contatti OStatus" +msgid "Subscribing to OStatus contacts" +msgstr "" #: mod/ostatus_subscribe.php:25 msgid "No contact provided." @@ -937,8 +921,8 @@ msgstr "ignorato" msgid "Keep this window open until done." msgstr "Tieni questa finestra aperta fino a che ha finito." -#: mod/filer.php:30 include/conversation.php:1133 -#: include/conversation.php:1151 +#: mod/filer.php:30 include/conversation.php:1132 +#: include/conversation.php:1150 msgid "Save to Folder:" msgstr "Salva nella Cartella:" @@ -951,54 +935,54 @@ msgstr "- seleziona -" msgid "Save" msgstr "Salva" -#: mod/follow.php:18 mod/dfrn_request.php:863 +#: mod/follow.php:19 mod/dfrn_request.php:870 msgid "Submit Request" msgstr "Invia richiesta" -#: mod/follow.php:29 +#: mod/follow.php:30 msgid "You already added this contact." msgstr "Hai già aggiunto questo contatto." -#: mod/follow.php:38 +#: mod/follow.php:39 msgid "Diaspora support isn't enabled. Contact can't be added." msgstr "Il supporto Diaspora non è abilitato. Il contatto non puo' essere aggiunto." -#: mod/follow.php:45 +#: mod/follow.php:46 msgid "OStatus support is disabled. Contact can't be added." msgstr "Il supporto OStatus non è abilitato. Il contatto non puo' essere aggiunto." -#: mod/follow.php:52 +#: mod/follow.php:53 msgid "The network type couldn't be detected. Contact can't be added." msgstr "Non è possibile rilevare il tipo di rete. Il contatto non puo' essere aggiunto." -#: mod/follow.php:104 mod/dfrn_request.php:849 +#: mod/follow.php:109 mod/dfrn_request.php:856 msgid "Please answer the following:" msgstr "Rispondi:" -#: mod/follow.php:105 mod/dfrn_request.php:850 +#: mod/follow.php:110 mod/dfrn_request.php:857 #, php-format msgid "Does %s know you?" msgstr "%s ti conosce?" -#: mod/follow.php:105 mod/settings.php:1094 mod/settings.php:1100 -#: mod/settings.php:1108 mod/settings.php:1112 mod/settings.php:1117 -#: mod/settings.php:1123 mod/settings.php:1129 mod/settings.php:1135 -#: mod/settings.php:1161 mod/settings.php:1162 mod/settings.php:1163 -#: mod/settings.php:1164 mod/settings.php:1165 mod/dfrn_request.php:850 +#: mod/follow.php:110 mod/settings.php:1103 mod/settings.php:1109 +#: mod/settings.php:1117 mod/settings.php:1121 mod/settings.php:1126 +#: mod/settings.php:1132 mod/settings.php:1138 mod/settings.php:1144 +#: mod/settings.php:1170 mod/settings.php:1171 mod/settings.php:1172 +#: mod/settings.php:1173 mod/settings.php:1174 mod/dfrn_request.php:857 #: mod/register.php:239 mod/profiles.php:658 mod/profiles.php:662 #: mod/profiles.php:687 mod/api.php:106 msgid "No" msgstr "No" -#: mod/follow.php:106 mod/dfrn_request.php:854 +#: mod/follow.php:111 mod/dfrn_request.php:861 msgid "Add a personal note:" msgstr "Aggiungi una nota personale:" -#: mod/follow.php:112 mod/dfrn_request.php:860 +#: mod/follow.php:117 mod/dfrn_request.php:867 msgid "Your Identity Address:" msgstr "L'indirizzo della tua identità:" -#: mod/follow.php:162 +#: mod/follow.php:180 msgid "Contact added" msgstr "Contatto aggiunto" @@ -1006,39 +990,39 @@ msgstr "Contatto aggiunto" msgid "Unable to locate original post." msgstr "Impossibile trovare il messaggio originale." -#: mod/item.php:322 +#: mod/item.php:329 msgid "Empty post discarded." msgstr "Messaggio vuoto scartato." -#: mod/item.php:460 mod/wall_upload.php:213 mod/wall_upload.php:227 -#: mod/wall_upload.php:234 include/Photo.php:954 include/Photo.php:969 -#: include/Photo.php:976 include/Photo.php:998 include/message.php:145 +#: mod/item.php:467 mod/wall_upload.php:213 mod/wall_upload.php:227 +#: mod/wall_upload.php:234 include/Photo.php:958 include/Photo.php:973 +#: include/Photo.php:980 include/Photo.php:1002 include/message.php:145 msgid "Wall Photos" msgstr "Foto della bacheca" -#: mod/item.php:834 +#: mod/item.php:842 msgid "System error. Post not saved." msgstr "Errore di sistema. Messaggio non salvato." -#: mod/item.php:963 +#: mod/item.php:971 #, php-format msgid "" "This message was sent to you by %s, a member of the Friendica social " "network." msgstr "Questo messaggio ti è stato inviato da %s, un membro del social network Friendica." -#: mod/item.php:965 +#: mod/item.php:973 #, php-format msgid "You may visit them online at %s" msgstr "Puoi visitarli online su %s" -#: mod/item.php:966 +#: mod/item.php:974 msgid "" "Please contact the sender by replying to this post if you do not wish to " "receive these messages." msgstr "Contatta il mittente rispondendo a questo post se non vuoi ricevere questi messaggi." -#: mod/item.php:970 +#: mod/item.php:978 #, php-format msgid "%s posted an update." msgstr "%s ha inviato un aggiornamento." @@ -1087,11 +1071,11 @@ msgstr "Modifica gruppo" msgid "Members" msgstr "Membri" -#: mod/group.php:193 mod/network.php:563 mod/content.php:130 +#: mod/group.php:193 mod/network.php:576 mod/content.php:130 msgid "Group is empty" msgstr "Il gruppo è vuoto" -#: mod/apps.php:7 index.php:225 +#: mod/apps.php:7 index.php:226 msgid "You must be logged in to use addons. " msgstr "Devi aver effettuato il login per usare gli addons." @@ -1148,7 +1132,7 @@ msgid "Unable to set contact photo." msgstr "Impossibile impostare la foto del contatto." #: mod/dfrn_confirm.php:487 include/conversation.php:185 -#: include/diaspora.php:636 +#: include/diaspora.php:637 #, php-format msgid "%1$s is now friends with %2$s" msgstr "%1$s e %2$s adesso sono amici" @@ -1189,7 +1173,7 @@ msgstr "Impossibile impostare le credenziali del tuo contatto sul nostro sistema msgid "Unable to update your contact profile details on our system" msgstr "Impossibile aggiornare i dettagli del tuo contatto sul nostro sistema" -#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:734 include/items.php:4270 +#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:741 include/items.php:4299 msgid "[Name Withheld]" msgstr "[Nome Nascosto]" @@ -1198,7 +1182,7 @@ msgstr "[Nome Nascosto]" msgid "%1$s has joined %2$s" msgstr "%1$s si è unito a %2$s" -#: mod/profile.php:21 include/identity.php:52 +#: mod/profile.php:21 include/identity.php:51 msgid "Requested profile is not available." msgstr "Profilo richiesto non disponibile." @@ -1222,7 +1206,7 @@ msgstr "Nessun video selezionato" msgid "Access to this item is restricted." msgstr "Questo oggetto non è visibile a tutti." -#: mod/videos.php:383 include/text.php:1465 +#: mod/videos.php:383 include/text.php:1472 msgid "View Video" msgstr "Guarda Video" @@ -1258,7 +1242,7 @@ msgstr "Suggerisci un amico a %s" #: mod/wall_upload.php:20 mod/wall_upload.php:33 mod/wall_upload.php:86 #: mod/wall_upload.php:122 mod/wall_upload.php:125 mod/wall_attach.php:17 -#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1733 +#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1781 msgid "Invalid request." msgstr "Richiesta non valida." @@ -1314,7 +1298,7 @@ msgid "" "Password reset failed." msgstr "La richiesta non può essere verificata. (Puoi averla già richiesta precendentemente). Reimpostazione password fallita." -#: mod/lostpass.php:109 boot.php:1307 +#: mod/lostpass.php:109 boot.php:1444 msgid "Password Reset" msgstr "Reimpostazione password" @@ -1388,37 +1372,6 @@ msgstr "Nome utente o email: " msgid "Reset" msgstr "Reimposta" -#: mod/like.php:170 include/conversation.php:122 include/conversation.php:258 -#: include/text.php:1991 view/theme/diabook/theme.php:463 -msgid "event" -msgstr "l'evento" - -#: mod/like.php:187 include/conversation.php:141 include/diaspora.php:2162 -#: view/theme/diabook/theme.php:480 -#, php-format -msgid "%1$s likes %2$s's %3$s" -msgstr "A %1$s piace %3$s di %2$s" - -#: mod/like.php:189 include/conversation.php:144 -#, php-format -msgid "%1$s doesn't like %2$s's %3$s" -msgstr "A %1$s non piace %3$s di %2$s" - -#: mod/like.php:191 -#, php-format -msgid "%1$s is attending %2$s's %3$s" -msgstr "%1$s parteciperà a %3$s di %2$s" - -#: mod/like.php:193 -#, php-format -msgid "%1$s is not attending %2$s's %3$s" -msgstr "%1$s non parteciperà a %3$s di %2$s" - -#: mod/like.php:195 -#, php-format -msgid "%1$s may attend %2$s's %3$s" -msgstr "%1$s forse parteciperà a %3$s di %2$s" - #: mod/ping.php:265 msgid "{0} wants to be your friend" msgstr "{0} vuole essere tuo amico" @@ -1448,11 +1401,11 @@ msgstr "Scarta" msgid "System" msgstr "Sistema" -#: mod/notifications.php:87 mod/admin.php:228 include/nav.php:154 +#: mod/notifications.php:87 mod/admin.php:390 include/nav.php:154 msgid "Network" msgstr "Rete" -#: mod/notifications.php:93 mod/network.php:381 +#: mod/notifications.php:93 mod/network.php:384 msgid "Personal" msgstr "Personale" @@ -1494,7 +1447,7 @@ msgstr "Invia una attività \"è ora amico con\"" msgid "if applicable" msgstr "se applicabile" -#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1113 +#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1308 msgid "Approve" msgstr "Approva" @@ -1544,8 +1497,8 @@ msgstr "Richiesta amicizia/connessione" msgid "New Follower" msgstr "Qualcuno inizia a seguirti" -#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:306 -#: include/identity.php:580 +#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:310 +#: include/identity.php:590 msgid "Gender:" msgstr "Genere:" @@ -1716,7 +1669,7 @@ msgstr "Conversazione rimossa." #: mod/message.php:290 mod/message.php:298 mod/message.php:427 #: mod/message.php:435 mod/wallmessage.php:127 mod/wallmessage.php:135 -#: include/conversation.php:1129 include/conversation.php:1147 +#: include/conversation.php:1128 include/conversation.php:1146 msgid "Please enter a link URL:" msgstr "Inserisci l'indirizzo del link:" @@ -1738,19 +1691,19 @@ msgid "Your message:" msgstr "Il tuo messaggio:" #: mod/message.php:339 mod/message.php:523 mod/wallmessage.php:154 -#: mod/editpost.php:110 include/conversation.php:1184 +#: mod/editpost.php:110 include/conversation.php:1183 msgid "Upload photo" msgstr "Carica foto" #: mod/message.php:340 mod/message.php:524 mod/wallmessage.php:155 -#: mod/editpost.php:114 include/conversation.php:1188 +#: mod/editpost.php:114 include/conversation.php:1187 msgid "Insert web link" msgstr "Inserisci link" #: mod/message.php:341 mod/message.php:526 mod/content.php:501 #: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:124 #: mod/photos.php:1610 object/Item.php:396 include/conversation.php:713 -#: include/conversation.php:1202 +#: include/conversation.php:1201 msgid "Please wait" msgstr "Attendi" @@ -1766,7 +1719,7 @@ msgstr "Messaggio non disponibile." msgid "Delete message" msgstr "Elimina il messaggio" -#: mod/message.php:507 mod/message.php:582 +#: mod/message.php:507 mod/message.php:584 msgid "Delete conversation" msgstr "Elimina la conversazione" @@ -1780,26 +1733,26 @@ msgstr "Nessuna comunicazione sicura disponibile, <strong>Potresti</strong> esse msgid "Send Reply" msgstr "Invia la risposta" -#: mod/message.php:555 +#: mod/message.php:557 #, php-format msgid "Unknown sender - %s" msgstr "Mittente sconosciuto - %s" -#: mod/message.php:558 +#: mod/message.php:560 #, php-format msgid "You and %s" msgstr "Tu e %s" -#: mod/message.php:561 +#: mod/message.php:563 #, php-format msgid "%s and You" msgstr "%s e Tu" -#: mod/message.php:585 +#: mod/message.php:587 msgid "D, d M Y - g:i A" msgstr "D d M Y - G:i" -#: mod/message.php:588 +#: mod/message.php:590 #, php-format msgid "%d message" msgid_plural "%d messages" @@ -1851,9 +1804,9 @@ msgstr "Ritorna alla modifica contatto" msgid "Refetch contact data" msgstr "Ricarica dati contatto" -#: mod/crepair.php:170 mod/admin.php:1111 mod/admin.php:1123 -#: mod/admin.php:1124 mod/admin.php:1137 mod/settings.php:652 -#: mod/settings.php:678 +#: mod/crepair.php:170 mod/admin.php:1306 mod/admin.php:1318 +#: mod/admin.php:1319 mod/admin.php:1332 mod/settings.php:661 +#: mod/settings.php:687 msgid "Name" msgstr "Nome" @@ -1903,7 +1856,7 @@ msgid "" "entries from this contact." msgstr "Imposta questo contatto come 'io remoto', questo farà si che friendica reinvii i nuovi messaggi da questo contatto." -#: mod/bookmarklet.php:12 boot.php:1293 include/nav.php:91 +#: mod/bookmarklet.php:12 boot.php:1430 include/nav.php:91 msgid "Login" msgstr "Accedi" @@ -1915,28 +1868,28 @@ msgstr "Il messaggio è stato creato" msgid "Access denied." msgstr "Accesso negato." -#: mod/dirfind.php:188 mod/allfriends.php:75 mod/match.php:85 -#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:209 +#: mod/dirfind.php:194 mod/allfriends.php:80 mod/match.php:85 +#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:212 msgid "Connect" msgstr "Connetti" -#: mod/dirfind.php:189 mod/allfriends.php:59 mod/match.php:70 -#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:307 -#: include/Contact.php:320 include/Contact.php:362 +#: mod/dirfind.php:195 mod/allfriends.php:64 mod/match.php:70 +#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:283 +#: include/Contact.php:296 include/Contact.php:338 #: include/conversation.php:912 include/conversation.php:926 msgid "View Profile" msgstr "Visualizza profilo" -#: mod/dirfind.php:218 +#: mod/dirfind.php:224 #, php-format msgid "People Search - %s" msgstr "Cerca persone - %s" -#: mod/dirfind.php:225 mod/match.php:105 +#: mod/dirfind.php:231 mod/match.php:105 msgid "No matches" msgstr "Nessun risultato" -#: mod/fbrowser.php:32 include/identity.php:693 include/nav.php:77 +#: mod/fbrowser.php:32 include/identity.php:702 include/nav.php:77 #: view/theme/diabook/theme.php:126 msgid "Photos" msgstr "Foto" @@ -1956,548 +1909,579 @@ msgstr "File" msgid "Contacts who are not members of a group" msgstr "Contatti che non sono membri di un gruppo" -#: mod/admin.php:80 +#: mod/admin.php:92 msgid "Theme settings updated." msgstr "Impostazioni del tema aggiornate." -#: mod/admin.php:127 mod/admin.php:711 +#: mod/admin.php:156 mod/admin.php:888 msgid "Site" msgstr "Sito" -#: mod/admin.php:128 mod/admin.php:655 mod/admin.php:1106 mod/admin.php:1121 +#: mod/admin.php:157 mod/admin.php:832 mod/admin.php:1301 mod/admin.php:1316 msgid "Users" msgstr "Utenti" -#: mod/admin.php:129 mod/admin.php:1210 mod/admin.php:1270 mod/settings.php:66 +#: mod/admin.php:158 mod/admin.php:1416 mod/admin.php:1476 mod/settings.php:72 msgid "Plugins" msgstr "Plugin" -#: mod/admin.php:130 mod/admin.php:1455 mod/admin.php:1506 +#: mod/admin.php:159 mod/admin.php:1674 mod/admin.php:1724 msgid "Themes" msgstr "Temi" -#: mod/admin.php:131 +#: mod/admin.php:160 mod/settings.php:50 +msgid "Additional features" +msgstr "Funzionalità aggiuntive" + +#: mod/admin.php:161 msgid "DB updates" msgstr "Aggiornamenti Database" -#: mod/admin.php:132 mod/admin.php:223 +#: mod/admin.php:162 mod/admin.php:385 msgid "Inspect Queue" msgstr "Ispeziona Coda di invio" -#: mod/admin.php:147 mod/admin.php:156 mod/admin.php:1594 +#: mod/admin.php:163 mod/admin.php:354 +msgid "Federation Statistics" +msgstr "" + +#: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1792 msgid "Logs" msgstr "Log" -#: mod/admin.php:148 +#: mod/admin.php:178 mod/admin.php:1859 +msgid "View Logs" +msgstr "" + +#: mod/admin.php:179 msgid "probe address" msgstr "controlla indirizzo" -#: mod/admin.php:149 +#: mod/admin.php:180 msgid "check webfinger" msgstr "verifica webfinger" -#: mod/admin.php:154 include/nav.php:194 +#: mod/admin.php:186 include/nav.php:194 msgid "Admin" msgstr "Amministrazione" -#: mod/admin.php:155 +#: mod/admin.php:187 msgid "Plugin Features" msgstr "Impostazioni Plugins" -#: mod/admin.php:157 +#: mod/admin.php:189 msgid "diagnostics" msgstr "diagnostiche" -#: mod/admin.php:158 +#: mod/admin.php:190 msgid "User registrations waiting for confirmation" msgstr "Utenti registrati in attesa di conferma" -#: mod/admin.php:222 mod/admin.php:272 mod/admin.php:710 mod/admin.php:1105 -#: mod/admin.php:1209 mod/admin.php:1269 mod/admin.php:1454 mod/admin.php:1505 -#: mod/admin.php:1593 +#: mod/admin.php:347 +msgid "" +"This page offers you some numbers to the known part of the federated social " +"network your Friendica node is part of. These numbers are not complete but " +"only reflect the part of the network your node is aware of." +msgstr "" + +#: mod/admin.php:348 +msgid "" +"The <em>Auto Discovered Contact Directory</em> feature is not enabled, it " +"will improve the data displayed here." +msgstr "" + +#: mod/admin.php:353 mod/admin.php:384 mod/admin.php:441 mod/admin.php:887 +#: mod/admin.php:1300 mod/admin.php:1415 mod/admin.php:1475 mod/admin.php:1673 +#: mod/admin.php:1723 mod/admin.php:1791 mod/admin.php:1858 msgid "Administration" msgstr "Amministrazione" -#: mod/admin.php:225 +#: mod/admin.php:360 +#, php-format +msgid "Currently this node is aware of %d nodes from the following platforms:" +msgstr "" + +#: mod/admin.php:387 msgid "ID" msgstr "ID" -#: mod/admin.php:226 +#: mod/admin.php:388 msgid "Recipient Name" msgstr "Nome Destinatario" -#: mod/admin.php:227 +#: mod/admin.php:389 msgid "Recipient Profile" msgstr "Profilo Destinatario" -#: mod/admin.php:229 +#: mod/admin.php:391 msgid "Created" msgstr "Creato" -#: mod/admin.php:230 +#: mod/admin.php:392 msgid "Last Tried" msgstr "Ultimo Tentativo" -#: mod/admin.php:231 +#: mod/admin.php:393 msgid "" "This page lists the content of the queue for outgoing postings. These are " "postings the initial delivery failed for. They will be resend later and " "eventually deleted if the delivery fails permanently." msgstr "Questa pagina elenca il contenuto della coda di invo dei post. Questi sono post la cui consegna è fallita. Verranno reinviati più tardi ed eventualmente cancellati se la consegna continua a fallire." -#: mod/admin.php:243 mod/admin.php:1059 +#: mod/admin.php:412 mod/admin.php:1254 msgid "Normal Account" msgstr "Account normale" -#: mod/admin.php:244 mod/admin.php:1060 +#: mod/admin.php:413 mod/admin.php:1255 msgid "Soapbox Account" msgstr "Account per comunicati e annunci" -#: mod/admin.php:245 mod/admin.php:1061 +#: mod/admin.php:414 mod/admin.php:1256 msgid "Community/Celebrity Account" msgstr "Account per celebrità o per comunità" -#: mod/admin.php:246 mod/admin.php:1062 +#: mod/admin.php:415 mod/admin.php:1257 msgid "Automatic Friend Account" msgstr "Account per amicizia automatizzato" -#: mod/admin.php:247 +#: mod/admin.php:416 msgid "Blog Account" msgstr "Account Blog" -#: mod/admin.php:248 +#: mod/admin.php:417 msgid "Private Forum" msgstr "Forum Privato" -#: mod/admin.php:267 +#: mod/admin.php:436 msgid "Message queues" msgstr "Code messaggi" -#: mod/admin.php:273 +#: mod/admin.php:442 msgid "Summary" msgstr "Sommario" -#: mod/admin.php:275 +#: mod/admin.php:444 msgid "Registered users" msgstr "Utenti registrati" -#: mod/admin.php:277 +#: mod/admin.php:446 msgid "Pending registrations" msgstr "Registrazioni in attesa" -#: mod/admin.php:278 +#: mod/admin.php:447 msgid "Version" msgstr "Versione" -#: mod/admin.php:283 +#: mod/admin.php:452 msgid "Active plugins" msgstr "Plugin attivi" -#: mod/admin.php:306 +#: mod/admin.php:475 msgid "Can not parse base url. Must have at least <scheme>://<domain>" msgstr "Impossibile analizzare l'url base. Deve avere almeno [schema]://[dominio]" -#: mod/admin.php:587 +#: mod/admin.php:760 msgid "RINO2 needs mcrypt php extension to work." msgstr "RINO2 necessita dell'estensione php mcrypt per funzionare." -#: mod/admin.php:595 +#: mod/admin.php:768 msgid "Site settings updated." msgstr "Impostazioni del sito aggiornate." -#: mod/admin.php:619 mod/settings.php:903 +#: mod/admin.php:796 mod/settings.php:912 msgid "No special theme for mobile devices" msgstr "Nessun tema speciale per i dispositivi mobili" -#: mod/admin.php:638 +#: mod/admin.php:815 msgid "No community page" msgstr "Nessuna pagina Comunità" -#: mod/admin.php:639 +#: mod/admin.php:816 msgid "Public postings from users of this site" msgstr "Messaggi pubblici dagli utenti di questo sito" -#: mod/admin.php:640 +#: mod/admin.php:817 msgid "Global community page" msgstr "Pagina Comunità globale" -#: mod/admin.php:646 +#: mod/admin.php:823 msgid "At post arrival" msgstr "All'arrivo di un messaggio" -#: mod/admin.php:647 include/contact_selectors.php:56 +#: mod/admin.php:824 include/contact_selectors.php:56 msgid "Frequently" msgstr "Frequentemente" -#: mod/admin.php:648 include/contact_selectors.php:57 +#: mod/admin.php:825 include/contact_selectors.php:57 msgid "Hourly" msgstr "Ogni ora" -#: mod/admin.php:649 include/contact_selectors.php:58 +#: mod/admin.php:826 include/contact_selectors.php:58 msgid "Twice daily" msgstr "Due volte al dì" -#: mod/admin.php:650 include/contact_selectors.php:59 +#: mod/admin.php:827 include/contact_selectors.php:59 msgid "Daily" msgstr "Giornalmente" -#: mod/admin.php:656 +#: mod/admin.php:833 msgid "Users, Global Contacts" msgstr "Utenti, Contatti Globali" -#: mod/admin.php:657 +#: mod/admin.php:834 msgid "Users, Global Contacts/fallback" msgstr "Utenti, Contatti Globali/fallback" -#: mod/admin.php:661 +#: mod/admin.php:838 msgid "One month" msgstr "Un mese" -#: mod/admin.php:662 +#: mod/admin.php:839 msgid "Three months" msgstr "Tre mesi" -#: mod/admin.php:663 +#: mod/admin.php:840 msgid "Half a year" msgstr "Sei mesi" -#: mod/admin.php:664 +#: mod/admin.php:841 msgid "One year" msgstr "Un anno" -#: mod/admin.php:669 +#: mod/admin.php:846 msgid "Multi user instance" msgstr "Istanza multi utente" -#: mod/admin.php:692 +#: mod/admin.php:869 msgid "Closed" msgstr "Chiusa" -#: mod/admin.php:693 +#: mod/admin.php:870 msgid "Requires approval" msgstr "Richiede l'approvazione" -#: mod/admin.php:694 +#: mod/admin.php:871 msgid "Open" msgstr "Aperta" -#: mod/admin.php:698 +#: mod/admin.php:875 msgid "No SSL policy, links will track page SSL state" msgstr "Nessuna gestione SSL, i link seguiranno lo stato SSL della pagina" -#: mod/admin.php:699 +#: mod/admin.php:876 msgid "Force all links to use SSL" msgstr "Forza tutti i linki ad usare SSL" -#: mod/admin.php:700 +#: mod/admin.php:877 msgid "Self-signed certificate, use SSL for local links only (discouraged)" msgstr "Certificato auto-firmato, usa SSL solo per i link locali (sconsigliato)" -#: mod/admin.php:712 mod/admin.php:1271 mod/admin.php:1507 mod/admin.php:1595 -#: mod/settings.php:650 mod/settings.php:760 mod/settings.php:804 -#: mod/settings.php:873 mod/settings.php:960 mod/settings.php:1195 +#: mod/admin.php:889 mod/admin.php:1477 mod/admin.php:1725 mod/admin.php:1793 +#: mod/admin.php:1942 mod/settings.php:659 mod/settings.php:769 +#: mod/settings.php:813 mod/settings.php:882 mod/settings.php:969 +#: mod/settings.php:1204 msgid "Save Settings" msgstr "Salva Impostazioni" -#: mod/admin.php:713 mod/register.php:263 +#: mod/admin.php:890 mod/register.php:263 msgid "Registration" msgstr "Registrazione" -#: mod/admin.php:714 +#: mod/admin.php:891 msgid "File upload" msgstr "Caricamento file" -#: mod/admin.php:715 +#: mod/admin.php:892 msgid "Policies" msgstr "Politiche" -#: mod/admin.php:716 +#: mod/admin.php:893 msgid "Advanced" msgstr "Avanzate" -#: mod/admin.php:717 +#: mod/admin.php:894 msgid "Auto Discovered Contact Directory" msgstr "Elenco Contatti Scoperto Automaticamente" -#: mod/admin.php:718 +#: mod/admin.php:895 msgid "Performance" msgstr "Performance" -#: mod/admin.php:719 +#: mod/admin.php:896 msgid "" "Relocate - WARNING: advanced function. Could make this server unreachable." msgstr "Trasloca - ATTENZIONE: funzione avanzata! Puo' rendere questo server irraggiungibile." -#: mod/admin.php:722 +#: mod/admin.php:899 msgid "Site name" msgstr "Nome del sito" -#: mod/admin.php:723 +#: mod/admin.php:900 msgid "Host name" msgstr "Nome host" -#: mod/admin.php:724 +#: mod/admin.php:901 msgid "Sender Email" msgstr "Mittente email" -#: mod/admin.php:724 +#: mod/admin.php:901 msgid "" "The email address your server shall use to send notification emails from." msgstr "L'indirizzo email che il tuo server dovrà usare per inviare notifiche via email." -#: mod/admin.php:725 +#: mod/admin.php:902 msgid "Banner/Logo" msgstr "Banner/Logo" -#: mod/admin.php:726 +#: mod/admin.php:903 msgid "Shortcut icon" msgstr "Icona shortcut" -#: mod/admin.php:726 +#: mod/admin.php:903 msgid "Link to an icon that will be used for browsers." msgstr "Link verso un'icona che verrà usata dai browsers." -#: mod/admin.php:727 +#: mod/admin.php:904 msgid "Touch icon" msgstr "Icona touch" -#: mod/admin.php:727 +#: mod/admin.php:904 msgid "Link to an icon that will be used for tablets and mobiles." msgstr "Link verso un'icona che verrà usata dai tablet e i telefonini." -#: mod/admin.php:728 +#: mod/admin.php:905 msgid "Additional Info" msgstr "Informazioni aggiuntive" -#: mod/admin.php:728 +#: mod/admin.php:905 #, php-format msgid "" "For public servers: you can add additional information here that will be " "listed at %s/siteinfo." msgstr "Per server pubblici: puoi aggiungere informazioni extra che verrano mostrate su %s/siteinfo." -#: mod/admin.php:729 +#: mod/admin.php:906 msgid "System language" msgstr "Lingua di sistema" -#: mod/admin.php:730 +#: mod/admin.php:907 msgid "System theme" msgstr "Tema di sistema" -#: mod/admin.php:730 +#: mod/admin.php:907 msgid "" "Default system theme - may be over-ridden by user profiles - <a href='#' " "id='cnftheme'>change theme settings</a>" msgstr "Tema di sistema - puo' essere sovrascritto dalle impostazioni utente - <a href='#' id='cnftheme'>cambia le impostazioni del tema</a>" -#: mod/admin.php:731 +#: mod/admin.php:908 msgid "Mobile system theme" msgstr "Tema mobile di sistema" -#: mod/admin.php:731 +#: mod/admin.php:908 msgid "Theme for mobile devices" msgstr "Tema per dispositivi mobili" -#: mod/admin.php:732 +#: mod/admin.php:909 msgid "SSL link policy" msgstr "Gestione link SSL" -#: mod/admin.php:732 +#: mod/admin.php:909 msgid "Determines whether generated links should be forced to use SSL" msgstr "Determina se i link generati devono essere forzati a usare SSL" -#: mod/admin.php:733 +#: mod/admin.php:910 msgid "Force SSL" msgstr "Forza SSL" -#: mod/admin.php:733 +#: mod/admin.php:910 msgid "" "Force all Non-SSL requests to SSL - Attention: on some systems it could lead" " to endless loops." msgstr "Forza tutte le richieste non SSL su SSL - Attenzione: su alcuni sistemi puo' portare a loop senza fine" -#: mod/admin.php:734 +#: mod/admin.php:911 msgid "Old style 'Share'" msgstr "Ricondivisione vecchio stile" -#: mod/admin.php:734 +#: mod/admin.php:911 msgid "Deactivates the bbcode element 'share' for repeating items." msgstr "Disattiva l'elemento bbcode 'share' con elementi ripetuti" -#: mod/admin.php:735 +#: mod/admin.php:912 msgid "Hide help entry from navigation menu" msgstr "Nascondi la voce 'Guida' dal menu di navigazione" -#: mod/admin.php:735 +#: mod/admin.php:912 msgid "" "Hides the menu entry for the Help pages from the navigation menu. You can " "still access it calling /help directly." msgstr "Nasconde la voce per le pagine della guida dal menu di navigazione. E' comunque possibile accedervi richiamando /help direttamente." -#: mod/admin.php:736 +#: mod/admin.php:913 msgid "Single user instance" msgstr "Instanza a singolo utente" -#: mod/admin.php:736 +#: mod/admin.php:913 msgid "Make this instance multi-user or single-user for the named user" msgstr "Rendi questa istanza multi utente o a singolo utente per l'utente selezionato" -#: mod/admin.php:737 +#: mod/admin.php:914 msgid "Maximum image size" msgstr "Massima dimensione immagini" -#: mod/admin.php:737 +#: mod/admin.php:914 msgid "" "Maximum size in bytes of uploaded images. Default is 0, which means no " "limits." msgstr "Massima dimensione in byte delle immagini caricate. Il default è 0, cioè nessun limite." -#: mod/admin.php:738 +#: mod/admin.php:915 msgid "Maximum image length" msgstr "Massima lunghezza immagine" -#: mod/admin.php:738 +#: mod/admin.php:915 msgid "" "Maximum length in pixels of the longest side of uploaded images. Default is " "-1, which means no limits." msgstr "Massima lunghezza in pixel del lato più lungo delle immagini caricate. Predefinito a -1, ovvero nessun limite." -#: mod/admin.php:739 +#: mod/admin.php:916 msgid "JPEG image quality" msgstr "Qualità immagini JPEG" -#: mod/admin.php:739 +#: mod/admin.php:916 msgid "" "Uploaded JPEGS will be saved at this quality setting [0-100]. Default is " "100, which is full quality." msgstr "Le immagini JPEG caricate verranno salvate con questa qualità [0-100]. Predefinito è 100, ovvero qualità piena." -#: mod/admin.php:741 +#: mod/admin.php:918 msgid "Register policy" msgstr "Politica di registrazione" -#: mod/admin.php:742 +#: mod/admin.php:919 msgid "Maximum Daily Registrations" msgstr "Massime registrazioni giornaliere" -#: mod/admin.php:742 +#: mod/admin.php:919 msgid "" "If registration is permitted above, this sets the maximum number of new user" " registrations to accept per day. If register is set to closed, this " "setting has no effect." msgstr "Se la registrazione è permessa, qui si definisce il massimo numero di nuovi utenti registrati da accettare giornalmente. Se la registrazione è chiusa, questa impostazione non ha effetto." -#: mod/admin.php:743 +#: mod/admin.php:920 msgid "Register text" msgstr "Testo registrazione" -#: mod/admin.php:743 +#: mod/admin.php:920 msgid "Will be displayed prominently on the registration page." msgstr "Sarà mostrato ben visibile nella pagina di registrazione." -#: mod/admin.php:744 +#: mod/admin.php:921 msgid "Accounts abandoned after x days" msgstr "Account abbandonati dopo x giorni" -#: mod/admin.php:744 +#: mod/admin.php:921 msgid "" "Will not waste system resources polling external sites for abandonded " "accounts. Enter 0 for no time limit." msgstr "Non spreca risorse di sistema controllando siti esterni per gli account abbandonati. Immettere 0 per nessun limite di tempo." -#: mod/admin.php:745 +#: mod/admin.php:922 msgid "Allowed friend domains" msgstr "Domini amici consentiti" -#: mod/admin.php:745 +#: mod/admin.php:922 msgid "" "Comma separated list of domains which are allowed to establish friendships " "with this site. Wildcards are accepted. Empty to allow any domains" msgstr "Elenco separato da virglola dei domini che possono stabilire amicizie con questo sito. Sono accettati caratteri jolly. Lascalo vuoto per accettare qualsiasi dominio." -#: mod/admin.php:746 +#: mod/admin.php:923 msgid "Allowed email domains" msgstr "Domini email consentiti" -#: mod/admin.php:746 +#: mod/admin.php:923 msgid "" "Comma separated list of domains which are allowed in email addresses for " "registrations to this site. Wildcards are accepted. Empty to allow any " "domains" msgstr "Elenco separato da virgola dei domini permessi come indirizzi email in fase di registrazione a questo sito. Sono accettati caratteri jolly. Lascalo vuoto per accettare qualsiasi dominio." -#: mod/admin.php:747 +#: mod/admin.php:924 msgid "Block public" msgstr "Blocca pagine pubbliche" -#: mod/admin.php:747 +#: mod/admin.php:924 msgid "" "Check to block public access to all otherwise public personal pages on this " "site unless you are currently logged in." msgstr "Seleziona per bloccare l'accesso pubblico a tutte le pagine personali di questo sito, a meno di essere loggato." -#: mod/admin.php:748 +#: mod/admin.php:925 msgid "Force publish" msgstr "Forza publicazione" -#: mod/admin.php:748 +#: mod/admin.php:925 msgid "" "Check to force all profiles on this site to be listed in the site directory." msgstr "Seleziona per forzare tutti i profili di questo sito ad essere compresi nell'elenco di questo sito." -#: mod/admin.php:749 +#: mod/admin.php:926 msgid "Global directory URL" msgstr "URL della directory globale" -#: mod/admin.php:749 +#: mod/admin.php:926 msgid "" "URL to the global directory. If this is not set, the global directory is " "completely unavailable to the application." msgstr "URL dell'elenco globale. Se vuoto, l'elenco globale sarà completamente disabilitato." -#: mod/admin.php:750 +#: mod/admin.php:927 msgid "Allow threaded items" msgstr "Permetti commenti nidificati" -#: mod/admin.php:750 +#: mod/admin.php:927 msgid "Allow infinite level threading for items on this site." msgstr "Permette un infinito livello di nidificazione dei commenti su questo sito." -#: mod/admin.php:751 +#: mod/admin.php:928 msgid "Private posts by default for new users" msgstr "Post privati di default per i nuovi utenti" -#: mod/admin.php:751 +#: mod/admin.php:928 msgid "" "Set default post permissions for all new members to the default privacy " "group rather than public." msgstr "Imposta i permessi predefiniti dei post per tutti i nuovi utenti come privati per il gruppo predefinito, invece che pubblici." -#: mod/admin.php:752 +#: mod/admin.php:929 msgid "Don't include post content in email notifications" msgstr "Non includere il contenuto dei post nelle notifiche via email" -#: mod/admin.php:752 +#: mod/admin.php:929 msgid "" "Don't include the content of a post/comment/private message/etc. in the " "email notifications that are sent out from this site, as a privacy measure." msgstr "Non include il contenuti del post/commento/messaggio privato/etc. nelle notifiche email che sono inviate da questo sito, per privacy" -#: mod/admin.php:753 +#: mod/admin.php:930 msgid "Disallow public access to addons listed in the apps menu." msgstr "Disabilita l'accesso pubblico ai plugin raccolti nel menu apps." -#: mod/admin.php:753 +#: mod/admin.php:930 msgid "" "Checking this box will restrict addons listed in the apps menu to members " "only." msgstr "Selezionando questo box si limiterà ai soli membri l'accesso agli addon nel menu applicazioni" -#: mod/admin.php:754 +#: mod/admin.php:931 msgid "Don't embed private images in posts" msgstr "Non inglobare immagini private nei post" -#: mod/admin.php:754 +#: mod/admin.php:931 msgid "" "Don't replace locally-hosted private photos in posts with an embedded copy " "of the image. This means that contacts who receive posts containing private " @@ -2505,218 +2489,228 @@ msgid "" "while." msgstr "Non sostituire le foto locali nei post con una copia incorporata dell'immagine. Questo significa che i contatti che riceveranno i post contenenti foto private dovranno autenticarsi e caricare ogni immagine, cosa che puo' richiedere un po' di tempo." -#: mod/admin.php:755 +#: mod/admin.php:932 msgid "Allow Users to set remote_self" msgstr "Permetti agli utenti di impostare 'io remoto'" -#: mod/admin.php:755 +#: mod/admin.php:932 msgid "" "With checking this, every user is allowed to mark every contact as a " "remote_self in the repair contact dialog. Setting this flag on a contact " "causes mirroring every posting of that contact in the users stream." msgstr "Selezionando questo, a tutti gli utenti sarà permesso di impostare qualsiasi contatto come 'io remoto' nella pagina di modifica del contatto. Impostare questa opzione fa si che tutti i messaggi di quel contatto vengano ripetuti nello stream del'utente." -#: mod/admin.php:756 +#: mod/admin.php:933 msgid "Block multiple registrations" msgstr "Blocca registrazioni multiple" -#: mod/admin.php:756 +#: mod/admin.php:933 msgid "Disallow users to register additional accounts for use as pages." msgstr "Non permette all'utente di registrare account extra da usare come pagine." -#: mod/admin.php:757 +#: mod/admin.php:934 msgid "OpenID support" msgstr "Supporto OpenID" -#: mod/admin.php:757 +#: mod/admin.php:934 msgid "OpenID support for registration and logins." msgstr "Supporta OpenID per la registrazione e il login" -#: mod/admin.php:758 +#: mod/admin.php:935 msgid "Fullname check" msgstr "Controllo nome completo" -#: mod/admin.php:758 +#: mod/admin.php:935 msgid "" "Force users to register with a space between firstname and lastname in Full " "name, as an antispam measure" msgstr "Forza gli utenti a registrarsi con uno spazio tra il nome e il cognome in \"Nome completo\", come misura antispam" -#: mod/admin.php:759 +#: mod/admin.php:936 msgid "UTF-8 Regular expressions" msgstr "Espressioni regolari UTF-8" -#: mod/admin.php:759 +#: mod/admin.php:936 msgid "Use PHP UTF8 regular expressions" msgstr "Usa le espressioni regolari PHP in UTF8" -#: mod/admin.php:760 +#: mod/admin.php:937 msgid "Community Page Style" msgstr "Stile pagina Comunità" -#: mod/admin.php:760 +#: mod/admin.php:937 msgid "" "Type of community page to show. 'Global community' shows every public " "posting from an open distributed network that arrived on this server." msgstr "Tipo di pagina Comunità da mostrare. 'Comunità Globale' mostra tutti i messaggi pubblici arrivati su questo server da network aperti distribuiti." -#: mod/admin.php:761 +#: mod/admin.php:938 msgid "Posts per user on community page" msgstr "Messaggi per utente nella pagina Comunità" -#: mod/admin.php:761 +#: mod/admin.php:938 msgid "" "The maximum number of posts per user on the community page. (Not valid for " "'Global Community')" msgstr "Il numero massimo di messaggi per utente mostrato nella pagina Comuntà (non valido per 'Comunità globale')" -#: mod/admin.php:762 +#: mod/admin.php:939 msgid "Enable OStatus support" msgstr "Abilita supporto OStatus" -#: mod/admin.php:762 +#: mod/admin.php:939 msgid "" "Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All " "communications in OStatus are public, so privacy warnings will be " "occasionally displayed." msgstr "Fornisce la compatibilità integrata a OStatus (StatusNet, Gnu Social, etc.). Tutte le comunicazioni su OStatus sono pubbliche, quindi un avviso di privacy verrà mostrato occasionalmente." -#: mod/admin.php:763 +#: mod/admin.php:940 msgid "OStatus conversation completion interval" msgstr "Intervallo completamento conversazioni OStatus" -#: mod/admin.php:763 +#: mod/admin.php:940 msgid "" "How often shall the poller check for new entries in OStatus conversations? " "This can be a very ressource task." msgstr "quanto spesso il poller deve controllare se esistono nuovi commenti in una conversazione OStatus? Questo è un lavoro che puo' richiedere molte risorse." -#: mod/admin.php:764 +#: mod/admin.php:941 msgid "OStatus support can only be enabled if threading is enabled." msgstr "Il supporto OStatus puo' essere abilitato solo se è abilitato il threading." -#: mod/admin.php:766 +#: mod/admin.php:943 msgid "" "Diaspora support can't be enabled because Friendica was installed into a sub" " directory." msgstr "Il supporto a Diaspora non puo' essere abilitato perchè Friendica è stato installato in una sotto directory." -#: mod/admin.php:767 +#: mod/admin.php:944 msgid "Enable Diaspora support" msgstr "Abilita il supporto a Diaspora" -#: mod/admin.php:767 +#: mod/admin.php:944 msgid "Provide built-in Diaspora network compatibility." msgstr "Fornisce compatibilità con il network Diaspora." -#: mod/admin.php:768 +#: mod/admin.php:945 msgid "Only allow Friendica contacts" msgstr "Permetti solo contatti Friendica" -#: mod/admin.php:768 +#: mod/admin.php:945 msgid "" "All contacts must use Friendica protocols. All other built-in communication " "protocols disabled." msgstr "Tutti i contatti devono usare il protocollo di Friendica. Tutti gli altri protocolli sono disabilitati." -#: mod/admin.php:769 +#: mod/admin.php:946 msgid "Verify SSL" msgstr "Verifica SSL" -#: mod/admin.php:769 +#: mod/admin.php:946 msgid "" "If you wish, you can turn on strict certificate checking. This will mean you" " cannot connect (at all) to self-signed SSL sites." msgstr "Se vuoi, puoi abilitare il controllo rigoroso dei certificati.Questo significa che non potrai collegarti (del tutto) con siti con certificati SSL auto-firmati." -#: mod/admin.php:770 +#: mod/admin.php:947 msgid "Proxy user" msgstr "Utente Proxy" -#: mod/admin.php:771 +#: mod/admin.php:948 msgid "Proxy URL" msgstr "URL Proxy" -#: mod/admin.php:772 +#: mod/admin.php:949 msgid "Network timeout" msgstr "Timeout rete" -#: mod/admin.php:772 +#: mod/admin.php:949 msgid "Value is in seconds. Set to 0 for unlimited (not recommended)." msgstr "Valore in secondi. Imposta a 0 per illimitato (non raccomandato)." -#: mod/admin.php:773 +#: mod/admin.php:950 msgid "Delivery interval" msgstr "Intervallo di invio" -#: mod/admin.php:773 +#: mod/admin.php:950 msgid "" "Delay background delivery processes by this many seconds to reduce system " "load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 " "for large dedicated servers." msgstr "Ritarda il processo di invio in background di n secondi per ridurre il carico di sistema. Raccomandato: 4-5 per host condivisit, 2-3 per VPS. 0-1 per grandi server dedicati." -#: mod/admin.php:774 +#: mod/admin.php:951 msgid "Poll interval" msgstr "Intervallo di poll" -#: mod/admin.php:774 +#: mod/admin.php:951 msgid "" "Delay background polling processes by this many seconds to reduce system " "load. If 0, use delivery interval." msgstr "Ritarda il processo di poll in background di n secondi per ridurre il carico di sistema. Se 0, usa l'intervallo di invio." -#: mod/admin.php:775 +#: mod/admin.php:952 msgid "Maximum Load Average" msgstr "Massimo carico medio" -#: mod/admin.php:775 +#: mod/admin.php:952 msgid "" "Maximum system load before delivery and poll processes are deferred - " "default 50." msgstr "Massimo carico di sistema prima che i processi di invio e di poll siano ritardati. Predefinito a 50." -#: mod/admin.php:776 +#: mod/admin.php:953 msgid "Maximum Load Average (Frontend)" msgstr "Media Massimo Carico (Frontend)" -#: mod/admin.php:776 +#: mod/admin.php:953 msgid "Maximum system load before the frontend quits service - default 50." msgstr "Massimo carico di sistema prima che il frontend fermi il servizio - default 50." -#: mod/admin.php:777 +#: mod/admin.php:954 msgid "Maximum table size for optimization" msgstr "Dimensione massima della tabella per l'ottimizzazione" -#: mod/admin.php:777 +#: mod/admin.php:954 msgid "" "Maximum table size (in MB) for the automatic optimization - default 100 MB. " "Enter -1 to disable it." msgstr "La dimensione massima (in MB) per l'ottimizzazione automatica - default 100 MB. Inserisci -1 per disabilitarlo." -#: mod/admin.php:779 +#: mod/admin.php:955 +msgid "Minimum level of fragmentation" +msgstr "" + +#: mod/admin.php:955 +msgid "" +"Minimum fragmenation level to start the automatic optimization - default " +"value is 30%." +msgstr "" + +#: mod/admin.php:957 msgid "Periodical check of global contacts" msgstr "Check periodico dei contatti globali" -#: mod/admin.php:779 +#: mod/admin.php:957 msgid "" "If enabled, the global contacts are checked periodically for missing or " "outdated data and the vitality of the contacts and servers." msgstr "Se abilitato, i contatti globali sono controllati periodicamente per verificare dati mancanti o sorpassati e la vitaltà dei contatti e dei server." -#: mod/admin.php:780 +#: mod/admin.php:958 msgid "Days between requery" msgstr "Giorni tra le richieste" -#: mod/admin.php:780 +#: mod/admin.php:958 msgid "Number of days after which a server is requeried for his contacts." msgstr "Numero di giorni dopo i quali al server vengono richiesti i suoi contatti." -#: mod/admin.php:781 +#: mod/admin.php:959 msgid "Discover contacts from other servers" msgstr "Trova contatti dagli altri server" -#: mod/admin.php:781 +#: mod/admin.php:959 msgid "" "Periodically query other servers for contacts. You can choose between " "'users': the users on the remote system, 'Global Contacts': active contacts " @@ -2726,32 +2720,32 @@ msgid "" "Global Contacts'." msgstr "Richiede periodicamente contatti agli altri server. Puoi scegliere tra 'utenti', gli uenti sul sistema remoto, o 'contatti globali', i contatti attivi che sono conosciuti dal sistema. Il fallback è pensato per i server Redmatrix e i vecchi server Friendica, dove i contatti globali non sono disponibili. Il fallback incrementa il carico di sistema, per cui l'impostazione consigliata è \"Utenti, Contatti Globali\"." -#: mod/admin.php:782 +#: mod/admin.php:960 msgid "Timeframe for fetching global contacts" msgstr "Termine per il recupero contatti globali" -#: mod/admin.php:782 +#: mod/admin.php:960 msgid "" "When the discovery is activated, this value defines the timeframe for the " "activity of the global contacts that are fetched from other servers." msgstr "Quando si attiva la scoperta, questo valore definisce il periodo di tempo per l'attività dei contatti globali che vengono prelevati da altri server." -#: mod/admin.php:783 +#: mod/admin.php:961 msgid "Search the local directory" msgstr "Cerca la directory locale" -#: mod/admin.php:783 +#: mod/admin.php:961 msgid "" "Search the local directory instead of the global directory. When searching " "locally, every search will be executed on the global directory in the " "background. This improves the search results when the search is repeated." msgstr "Cerca nella directory locale invece che nella directory globale. Durante la ricerca a livello locale, ogni ricerca verrà eseguita sulla directory globale in background. Ciò migliora i risultati della ricerca quando la ricerca viene ripetuta." -#: mod/admin.php:785 +#: mod/admin.php:963 msgid "Publish server information" msgstr "Pubblica informazioni server" -#: mod/admin.php:785 +#: mod/admin.php:963 msgid "" "If enabled, general server and usage data will be published. The data " "contains the name and version of the server, number of users with public " @@ -2759,205 +2753,205 @@ msgid "" " href='http://the-federation.info/'>the-federation.info</a> for details." msgstr "Se abilitata, saranno pubblicati i dati generali del server e i dati di utilizzo. I dati contengono il nome e la versione del server, il numero di utenti con profili pubblici, numero dei posti e dei protocolli e connettori attivati. Per informazioni, vedere <a href='http://the-federation.info/'> the-federation.info </a>." -#: mod/admin.php:787 +#: mod/admin.php:965 msgid "Use MySQL full text engine" msgstr "Usa il motore MySQL full text" -#: mod/admin.php:787 +#: mod/admin.php:965 msgid "" "Activates the full text engine. Speeds up search - but can only search for " "four and more characters." msgstr "Attiva il motore full text. Velocizza la ricerca, ma puo' cercare solo per quattro o più caratteri." -#: mod/admin.php:788 +#: mod/admin.php:966 msgid "Suppress Language" msgstr "Disattiva lingua" -#: mod/admin.php:788 +#: mod/admin.php:966 msgid "Suppress language information in meta information about a posting." msgstr "Disattiva le informazioni sulla lingua nei meta di un post." -#: mod/admin.php:789 +#: mod/admin.php:967 msgid "Suppress Tags" msgstr "Sopprimi Tags" -#: mod/admin.php:789 +#: mod/admin.php:967 msgid "Suppress showing a list of hashtags at the end of the posting." msgstr "Non mostra la lista di hashtag in coda al messaggio" -#: mod/admin.php:790 +#: mod/admin.php:968 msgid "Path to item cache" msgstr "Percorso cache elementi" -#: mod/admin.php:790 +#: mod/admin.php:968 msgid "The item caches buffers generated bbcode and external images." msgstr "La cache degli elementi memorizza il bbcode generato e le immagini esterne." -#: mod/admin.php:791 +#: mod/admin.php:969 msgid "Cache duration in seconds" msgstr "Durata della cache in secondi" -#: mod/admin.php:791 +#: mod/admin.php:969 msgid "" "How long should the cache files be hold? Default value is 86400 seconds (One" " day). To disable the item cache, set the value to -1." msgstr "Quanto a lungo devono essere mantenuti i file di cache? Il valore predefinito è 86400 secondi (un giorno). Per disabilitare la cache, imposta il valore a -1." -#: mod/admin.php:792 +#: mod/admin.php:970 msgid "Maximum numbers of comments per post" msgstr "Numero massimo di commenti per post" -#: mod/admin.php:792 +#: mod/admin.php:970 msgid "How much comments should be shown for each post? Default value is 100." msgstr "Quanti commenti devono essere mostrati per ogni post? Default : 100." -#: mod/admin.php:793 +#: mod/admin.php:971 msgid "Path for lock file" msgstr "Percorso al file di lock" -#: mod/admin.php:793 +#: mod/admin.php:971 msgid "" "The lock file is used to avoid multiple pollers at one time. Only define a " "folder here." msgstr "Il file di lock è usato per evitare l'avvio di poller multipli allo stesso tempo. Inserisci solo la cartella, qui." -#: mod/admin.php:794 +#: mod/admin.php:972 msgid "Temp path" msgstr "Percorso file temporanei" -#: mod/admin.php:794 +#: mod/admin.php:972 msgid "" "If you have a restricted system where the webserver can't access the system " "temp path, enter another path here." msgstr "Se si dispone di un sistema ristretto in cui il server web non può accedere al percorso temporaneo di sistema, inserire un altro percorso qui." -#: mod/admin.php:795 +#: mod/admin.php:973 msgid "Base path to installation" msgstr "Percorso base all'installazione" -#: mod/admin.php:795 +#: mod/admin.php:973 msgid "" "If the system cannot detect the correct path to your installation, enter the" " correct path here. This setting should only be set if you are using a " "restricted system and symbolic links to your webroot." msgstr "Se il sistema non è in grado di rilevare il percorso corretto per l'installazione, immettere il percorso corretto qui. Questa impostazione deve essere inserita solo se si utilizza un sistema limitato e/o collegamenti simbolici al tuo webroot." -#: mod/admin.php:796 +#: mod/admin.php:974 msgid "Disable picture proxy" msgstr "Disabilita il proxy immagini" -#: mod/admin.php:796 +#: mod/admin.php:974 msgid "" "The picture proxy increases performance and privacy. It shouldn't be used on" " systems with very low bandwith." msgstr "Il proxy immagini aumenta le performace e la privacy. Non dovrebbe essere usato su server con poca banda disponibile." -#: mod/admin.php:797 +#: mod/admin.php:975 msgid "Enable old style pager" msgstr "Abilita la paginazione vecchio stile" -#: mod/admin.php:797 +#: mod/admin.php:975 msgid "" "The old style pager has page numbers but slows down massively the page " "speed." msgstr "La paginazione vecchio stile mostra i numeri delle pagine, ma rallenta la velocità di caricamento della pagina." -#: mod/admin.php:798 +#: mod/admin.php:976 msgid "Only search in tags" msgstr "Cerca solo nei tag" -#: mod/admin.php:798 +#: mod/admin.php:976 msgid "On large systems the text search can slow down the system extremely." msgstr "Su server con molti dati, la ricerca nel testo può estremamente rallentare il sistema." -#: mod/admin.php:800 +#: mod/admin.php:978 msgid "New base url" msgstr "Nuovo url base" -#: mod/admin.php:800 +#: mod/admin.php:978 msgid "" "Change base url for this server. Sends relocate message to all DFRN contacts" " of all users." msgstr "Cambia l'url base di questo server. Invia il messaggio di trasloco a tutti i contatti DFRN di tutti gli utenti." -#: mod/admin.php:802 +#: mod/admin.php:980 msgid "RINO Encryption" msgstr "Crittografia RINO" -#: mod/admin.php:802 +#: mod/admin.php:980 msgid "Encryption layer between nodes." msgstr "Crittografia delle comunicazioni tra nodi." -#: mod/admin.php:803 +#: mod/admin.php:981 msgid "Embedly API key" msgstr "Embedly API key" -#: mod/admin.php:803 +#: mod/admin.php:981 msgid "" "<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for " "web pages. This is an optional parameter." msgstr "<a href='http://embed.ly'>Embedly</a> è usato per recuperate informazioni addizionali dalle pagine web. Questo parametro è opzionale." -#: mod/admin.php:821 +#: mod/admin.php:1010 msgid "Update has been marked successful" msgstr "L'aggiornamento è stato segnato come di successo" -#: mod/admin.php:829 +#: mod/admin.php:1018 #, php-format msgid "Database structure update %s was successfully applied." msgstr "Aggiornamento struttura database %s applicata con successo." -#: mod/admin.php:832 +#: mod/admin.php:1021 #, php-format msgid "Executing of database structure update %s failed with error: %s" msgstr "Aggiornamento struttura database %s fallita con errore: %s" -#: mod/admin.php:844 +#: mod/admin.php:1033 #, php-format msgid "Executing %s failed with error: %s" msgstr "Esecuzione di %s fallita con errore: %s" -#: mod/admin.php:847 +#: mod/admin.php:1036 #, php-format msgid "Update %s was successfully applied." msgstr "L'aggiornamento %s è stato applicato con successo" -#: mod/admin.php:851 +#: mod/admin.php:1040 #, php-format msgid "Update %s did not return a status. Unknown if it succeeded." msgstr "L'aggiornamento %s non ha riportato uno stato. Non so se è andato a buon fine." -#: mod/admin.php:853 +#: mod/admin.php:1042 #, php-format msgid "There was no additional update function %s that needed to be called." msgstr "Non ci sono altre funzioni di aggiornamento %s da richiamare." -#: mod/admin.php:872 +#: mod/admin.php:1061 msgid "No failed updates." msgstr "Nessun aggiornamento fallito." -#: mod/admin.php:873 +#: mod/admin.php:1062 msgid "Check database structure" msgstr "Controlla struttura database" -#: mod/admin.php:878 +#: mod/admin.php:1067 msgid "Failed Updates" msgstr "Aggiornamenti falliti" -#: mod/admin.php:879 +#: mod/admin.php:1068 msgid "" "This does not include updates prior to 1139, which did not return a status." msgstr "Questo non include gli aggiornamenti prima del 1139, che non ritornano lo stato." -#: mod/admin.php:880 +#: mod/admin.php:1069 msgid "Mark success (if update was manually applied)" msgstr "Segna completato (se l'update è stato applicato manualmente)" -#: mod/admin.php:881 +#: mod/admin.php:1070 msgid "Attempt to execute this update step automatically" msgstr "Cerco di eseguire questo aggiornamento in automatico" -#: mod/admin.php:913 +#: mod/admin.php:1102 #, php-format msgid "" "\n" @@ -2965,7 +2959,7 @@ msgid "" "\t\t\t\tthe administrator of %2$s has set up an account for you." msgstr "\nGentile %1$s,\n l'amministratore di %2$s ha impostato un account per te." -#: mod/admin.php:916 +#: mod/admin.php:1105 #, php-format msgid "" "\n" @@ -2995,233 +2989,255 @@ msgid "" "\t\t\tThank you and welcome to %4$s." msgstr "\nI dettagli del tuo utente sono:\n Indirizzo del sito: %1$s\n Nome utente: %2$s\n Password: %3$s\n\nPuoi cambiare la tua password dalla pagina delle impostazioni del tuo account dopo esserti autenticato.\n\nPer favore, prenditi qualche momento per esaminare tutte le impostazioni presenti.\n\nPotresti voler aggiungere qualche informazione di base al tuo profilo predefinito (nella pagina \"Profili\"), così che le altre persone possano trovarti più facilmente.\n\nTi raccomandiamo di inserire il tuo nome completo, aggiungere una foto, aggiungere qualche parola chiave del profilo (molto utili per trovare nuovi contatti), e magari in quale nazione vivi, se non vuoi essere più specifico di così.\n\nNoi rispettiamo appieno la tua privacy, e nessuna di queste informazioni è necessaria o obbligatoria.\nSe sei nuovo e non conosci nessuno qui, possono aiutarti a trovare qualche nuovo e interessante contatto.\n\nGrazie e benvenuto su %4$s" -#: mod/admin.php:948 include/user.php:423 +#: mod/admin.php:1137 include/user.php:423 #, php-format msgid "Registration details for %s" msgstr "Dettagli della registrazione di %s" -#: mod/admin.php:960 +#: mod/admin.php:1149 #, php-format msgid "%s user blocked/unblocked" msgid_plural "%s users blocked/unblocked" msgstr[0] "%s utente bloccato/sbloccato" msgstr[1] "%s utenti bloccati/sbloccati" -#: mod/admin.php:967 +#: mod/admin.php:1156 #, php-format msgid "%s user deleted" msgid_plural "%s users deleted" msgstr[0] "%s utente cancellato" msgstr[1] "%s utenti cancellati" -#: mod/admin.php:1006 +#: mod/admin.php:1203 #, php-format msgid "User '%s' deleted" msgstr "Utente '%s' cancellato" -#: mod/admin.php:1014 +#: mod/admin.php:1211 #, php-format msgid "User '%s' unblocked" msgstr "Utente '%s' sbloccato" -#: mod/admin.php:1014 +#: mod/admin.php:1211 #, php-format msgid "User '%s' blocked" msgstr "Utente '%s' bloccato" -#: mod/admin.php:1107 +#: mod/admin.php:1302 msgid "Add User" msgstr "Aggiungi utente" -#: mod/admin.php:1108 +#: mod/admin.php:1303 msgid "select all" msgstr "seleziona tutti" -#: mod/admin.php:1109 +#: mod/admin.php:1304 msgid "User registrations waiting for confirm" msgstr "Richieste di registrazione in attesa di conferma" -#: mod/admin.php:1110 +#: mod/admin.php:1305 msgid "User waiting for permanent deletion" msgstr "Utente in attesa di cancellazione definitiva" -#: mod/admin.php:1111 +#: mod/admin.php:1306 msgid "Request date" msgstr "Data richiesta" -#: mod/admin.php:1111 mod/admin.php:1123 mod/admin.php:1124 mod/admin.php:1139 +#: mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319 mod/admin.php:1334 #: include/contact_selectors.php:79 include/contact_selectors.php:86 msgid "Email" msgstr "Email" -#: mod/admin.php:1112 +#: mod/admin.php:1307 msgid "No registrations." msgstr "Nessuna registrazione." -#: mod/admin.php:1114 +#: mod/admin.php:1309 msgid "Deny" msgstr "Nega" -#: mod/admin.php:1118 +#: mod/admin.php:1313 msgid "Site admin" msgstr "Amministrazione sito" -#: mod/admin.php:1119 +#: mod/admin.php:1314 msgid "Account expired" msgstr "Account scaduto" -#: mod/admin.php:1122 +#: mod/admin.php:1317 msgid "New User" msgstr "Nuovo Utente" -#: mod/admin.php:1123 mod/admin.php:1124 +#: mod/admin.php:1318 mod/admin.php:1319 msgid "Register date" msgstr "Data registrazione" -#: mod/admin.php:1123 mod/admin.php:1124 +#: mod/admin.php:1318 mod/admin.php:1319 msgid "Last login" msgstr "Ultimo accesso" -#: mod/admin.php:1123 mod/admin.php:1124 +#: mod/admin.php:1318 mod/admin.php:1319 msgid "Last item" msgstr "Ultimo elemento" -#: mod/admin.php:1123 +#: mod/admin.php:1318 msgid "Deleted since" msgstr "Rimosso da" -#: mod/admin.php:1124 mod/settings.php:41 +#: mod/admin.php:1319 mod/settings.php:41 msgid "Account" msgstr "Account" -#: mod/admin.php:1126 +#: mod/admin.php:1321 msgid "" "Selected users will be deleted!\\n\\nEverything these users had posted on " "this site will be permanently deleted!\\n\\nAre you sure?" msgstr "Gli utenti selezionati saranno cancellati!\\n\\nTutto quello che gli utenti hanno inviato su questo sito sarà permanentemente canellato!\\n\\nSei sicuro?" -#: mod/admin.php:1127 +#: mod/admin.php:1322 msgid "" "The user {0} will be deleted!\\n\\nEverything this user has posted on this " "site will be permanently deleted!\\n\\nAre you sure?" msgstr "L'utente {0} sarà cancellato!\\n\\nTutto quello che ha inviato su questo sito sarà permanentemente cancellato!\\n\\nSei sicuro?" -#: mod/admin.php:1137 +#: mod/admin.php:1332 msgid "Name of the new user." msgstr "Nome del nuovo utente." -#: mod/admin.php:1138 +#: mod/admin.php:1333 msgid "Nickname" msgstr "Nome utente" -#: mod/admin.php:1138 +#: mod/admin.php:1333 msgid "Nickname of the new user." msgstr "Nome utente del nuovo utente." -#: mod/admin.php:1139 +#: mod/admin.php:1334 msgid "Email address of the new user." msgstr "Indirizzo Email del nuovo utente." -#: mod/admin.php:1172 +#: mod/admin.php:1377 #, php-format msgid "Plugin %s disabled." msgstr "Plugin %s disabilitato." -#: mod/admin.php:1176 +#: mod/admin.php:1381 #, php-format msgid "Plugin %s enabled." msgstr "Plugin %s abilitato." -#: mod/admin.php:1186 mod/admin.php:1410 +#: mod/admin.php:1392 mod/admin.php:1628 msgid "Disable" msgstr "Disabilita" -#: mod/admin.php:1188 mod/admin.php:1412 +#: mod/admin.php:1394 mod/admin.php:1630 msgid "Enable" msgstr "Abilita" -#: mod/admin.php:1211 mod/admin.php:1456 +#: mod/admin.php:1417 mod/admin.php:1675 msgid "Toggle" msgstr "Inverti" -#: mod/admin.php:1219 mod/admin.php:1466 +#: mod/admin.php:1425 mod/admin.php:1684 msgid "Author: " msgstr "Autore: " -#: mod/admin.php:1220 mod/admin.php:1467 +#: mod/admin.php:1426 mod/admin.php:1685 msgid "Maintainer: " msgstr "Manutentore: " -#: mod/admin.php:1272 -#: view/smarty3/compiled/f835364006028b1061f37be121c9bd9db5fa50a9.file.admin_plugins.tpl.php:42 +#: mod/admin.php:1478 msgid "Reload active plugins" msgstr "Ricarica i plugin attivi" -#: mod/admin.php:1370 +#: mod/admin.php:1483 +#, php-format +msgid "" +"There are currently no plugins available on your node. You can find the " +"official plugin repository at %1$s and might find other interesting plugins " +"in the open plugin registry at %2$s" +msgstr "" + +#: mod/admin.php:1588 msgid "No themes found." msgstr "Nessun tema trovato." -#: mod/admin.php:1448 +#: mod/admin.php:1666 msgid "Screenshot" msgstr "Anteprima" -#: mod/admin.php:1508 +#: mod/admin.php:1726 msgid "Reload active themes" msgstr "Ricarica i temi attivi" -#: mod/admin.php:1512 +#: mod/admin.php:1731 +#, php-format +msgid "No themes found on the system. They should be paced in %1$s" +msgstr "" + +#: mod/admin.php:1732 msgid "[Experimental]" msgstr "[Sperimentale]" -#: mod/admin.php:1513 +#: mod/admin.php:1733 msgid "[Unsupported]" msgstr "[Non supportato]" -#: mod/admin.php:1540 +#: mod/admin.php:1757 msgid "Log settings updated." msgstr "Impostazioni Log aggiornate." -#: mod/admin.php:1596 +#: mod/admin.php:1794 msgid "Clear" msgstr "Pulisci" -#: mod/admin.php:1602 +#: mod/admin.php:1799 msgid "Enable Debugging" msgstr "Abilita Debugging" -#: mod/admin.php:1603 +#: mod/admin.php:1800 msgid "Log file" msgstr "File di Log" -#: mod/admin.php:1603 +#: mod/admin.php:1800 msgid "" "Must be writable by web server. Relative to your Friendica top-level " "directory." msgstr "Deve essere scrivibile dal server web. Relativo alla tua directory Friendica." -#: mod/admin.php:1604 +#: mod/admin.php:1801 msgid "Log level" msgstr "Livello di Log" -#: mod/admin.php:1654 include/acl_selectors.php:348 -msgid "Close" -msgstr "Chiudi" +#: mod/admin.php:1804 +msgid "PHP logging" +msgstr "" -#: mod/admin.php:1660 -msgid "FTP Host" -msgstr "Indirizzo FTP" +#: mod/admin.php:1805 +msgid "" +"To enable logging of PHP errors and warnings you can add the following to " +"the .htconfig.php file of your installation. The filename set in the " +"'error_log' line is relative to the friendica top-level directory and must " +"be writeable by the web server. The option '1' for 'log_errors' and " +"'display_errors' is to enable these options, set to '0' to disable them." +msgstr "" -#: mod/admin.php:1661 -msgid "FTP Path" -msgstr "Percorso FTP" +#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759 +msgid "Off" +msgstr "Spento" -#: mod/admin.php:1662 -msgid "FTP User" -msgstr "Utente FTP" +#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759 +msgid "On" +msgstr "Acceso" -#: mod/admin.php:1663 -msgid "FTP Password" -msgstr "Pasword FTP" +#: mod/admin.php:1932 +#, php-format +msgid "Lock feature %s" +msgstr "" + +#: mod/admin.php:1940 +msgid "Manage Additional Features" +msgstr "" #: mod/network.php:146 #, php-format @@ -3232,7 +3248,7 @@ msgstr "Risultato della ricerca per: %s" msgid "Remove term" msgstr "Rimuovi termine" -#: mod/network.php:200 mod/search.php:34 include/features.php:79 +#: mod/network.php:200 mod/search.php:34 include/features.php:84 msgid "Saved Searches" msgstr "Ricerche salvate" @@ -3240,51 +3256,51 @@ msgstr "Ricerche salvate" msgid "add" msgstr "aggiungi" -#: mod/network.php:362 +#: mod/network.php:365 msgid "Commented Order" msgstr "Ordina per commento" -#: mod/network.php:365 +#: mod/network.php:368 msgid "Sort by Comment Date" msgstr "Ordina per data commento" -#: mod/network.php:370 +#: mod/network.php:373 msgid "Posted Order" msgstr "Ordina per invio" -#: mod/network.php:373 +#: mod/network.php:376 msgid "Sort by Post Date" msgstr "Ordina per data messaggio" -#: mod/network.php:384 +#: mod/network.php:387 msgid "Posts that mention or involve you" msgstr "Messaggi che ti citano o coinvolgono" -#: mod/network.php:392 +#: mod/network.php:395 msgid "New" msgstr "Nuovo" -#: mod/network.php:395 +#: mod/network.php:398 msgid "Activity Stream - by date" msgstr "Activity Stream - per data" -#: mod/network.php:403 +#: mod/network.php:406 msgid "Shared Links" msgstr "Links condivisi" -#: mod/network.php:406 +#: mod/network.php:409 msgid "Interesting Links" msgstr "Link Interessanti" -#: mod/network.php:414 +#: mod/network.php:417 msgid "Starred" msgstr "Preferiti" -#: mod/network.php:417 +#: mod/network.php:420 msgid "Favourite Posts" msgstr "Messaggi preferiti" -#: mod/network.php:476 +#: mod/network.php:479 #, php-format msgid "Warning: This group contains %s member from an insecure network." msgid_plural "" @@ -3292,28 +3308,28 @@ msgid_plural "" msgstr[0] "Attenzione: questo gruppo contiene %s membro da un network insicuro." msgstr[1] "Attenzione: questo gruppo contiene %s membri da un network insicuro." -#: mod/network.php:479 +#: mod/network.php:482 msgid "Private messages to this group are at risk of public disclosure." msgstr "I messaggi privati su questo gruppo potrebbero risultare visibili anche pubblicamente." -#: mod/network.php:546 mod/content.php:119 +#: mod/network.php:549 mod/content.php:119 msgid "No such group" msgstr "Nessun gruppo" -#: mod/network.php:574 mod/content.php:135 +#: mod/network.php:580 mod/content.php:135 #, php-format msgid "Group: %s" msgstr "Gruppo: %s" -#: mod/network.php:606 +#: mod/network.php:608 msgid "Private messages to this person are at risk of public disclosure." msgstr "I messaggi privati a questa persona potrebbero risultare visibili anche pubblicamente." -#: mod/network.php:611 +#: mod/network.php:613 msgid "Invalid contact." msgstr "Contatto non valido." -#: mod/allfriends.php:38 +#: mod/allfriends.php:43 msgid "No friends to display." msgstr "Nessun amico da visualizzare." @@ -3353,11 +3369,11 @@ msgstr "Ven" msgid "Sat" msgstr "Sab" -#: mod/events.php:208 mod/settings.php:939 include/text.php:1274 +#: mod/events.php:208 mod/settings.php:948 include/text.php:1274 msgid "Sunday" msgstr "Domenica" -#: mod/events.php:209 mod/settings.php:939 include/text.php:1274 +#: mod/events.php:209 mod/settings.php:948 include/text.php:1274 msgid "Monday" msgstr "Lunedì" @@ -3497,11 +3513,11 @@ msgstr "l j F" msgid "Edit event" msgstr "Modifca l'evento" -#: mod/events.php:421 include/text.php:1721 include/text.php:1728 +#: mod/events.php:421 include/text.php:1728 include/text.php:1735 msgid "link to source" msgstr "Collegamento all'originale" -#: mod/events.php:456 include/identity.php:713 include/nav.php:79 +#: mod/events.php:456 include/identity.php:722 include/nav.php:79 #: include/nav.php:140 view/theme/diabook/theme.php:127 msgid "Events" msgstr "Eventi" @@ -3560,7 +3576,7 @@ msgstr "Condividi questo evento" #: mod/events.php:572 mod/content.php:721 mod/editpost.php:145 #: mod/photos.php:1631 mod/photos.php:1679 mod/photos.php:1767 -#: object/Item.php:719 include/conversation.php:1217 +#: object/Item.php:719 include/conversation.php:1216 msgid "Preview" msgstr "Anteprima" @@ -3604,15 +3620,15 @@ msgstr[0] "%d commento" msgstr[1] "%d commenti" #: mod/content.php:607 object/Item.php:421 object/Item.php:434 -#: include/text.php:1997 +#: include/text.php:2004 msgid "comment" msgid_plural "comments" msgstr[0] "" msgstr[1] "commento" -#: mod/content.php:608 boot.php:785 object/Item.php:422 +#: mod/content.php:608 boot.php:870 object/Item.php:422 #: include/contact_widgets.php:242 include/forums.php:110 -#: include/items.php:5178 view/theme/vier/theme.php:264 +#: include/items.php:5207 view/theme/vier/theme.php:264 msgid "show more" msgstr "mostra di più" @@ -3650,7 +3666,7 @@ msgid "This is you" msgstr "Questo sei tu" #: mod/content.php:711 mod/photos.php:1629 mod/photos.php:1677 -#: mod/photos.php:1765 boot.php:784 object/Item.php:393 object/Item.php:709 +#: mod/photos.php:1765 boot.php:869 object/Item.php:393 object/Item.php:709 msgid "Comment" msgstr "Commento" @@ -3686,7 +3702,7 @@ msgstr "Link" msgid "Video" msgstr "Video" -#: mod/content.php:730 mod/settings.php:712 object/Item.php:122 +#: mod/content.php:730 mod/settings.php:721 object/Item.php:122 #: object/Item.php:124 msgid "Edit" msgstr "Modifica" @@ -4082,19 +4098,19 @@ msgid "" "your site allow private mail from unknown senders." msgstr "Se vuoi che %s ti risponda, controlla che le tue impostazioni di privacy permettano la ricezione di messaggi privati da mittenti sconosciuti." -#: mod/help.php:31 +#: mod/help.php:41 msgid "Help:" msgstr "Guida:" -#: mod/help.php:36 include/nav.php:113 view/theme/vier/theme.php:302 +#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:302 msgid "Help" msgstr "Guida" -#: mod/help.php:42 mod/p.php:16 mod/p.php:25 index.php:269 +#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:270 msgid "Not Found" msgstr "Non trovato" -#: mod/help.php:45 index.php:272 +#: mod/help.php:56 index.php:273 msgid "Page not found." msgstr "Pagina non trovata." @@ -4141,16 +4157,16 @@ msgstr "Profili corrispondenti" msgid "link" msgstr "collegamento" -#: mod/community.php:23 +#: mod/community.php:27 msgid "Not available." msgstr "Non disponibile." -#: mod/community.php:32 include/nav.php:136 include/nav.php:138 +#: mod/community.php:36 include/nav.php:136 include/nav.php:138 #: view/theme/diabook/theme.php:129 msgid "Community" msgstr "Comunità" -#: mod/community.php:62 mod/community.php:71 mod/search.php:228 +#: mod/community.php:66 mod/community.php:75 mod/search.php:228 msgid "No results." msgstr "Nessun risultato." @@ -4158,822 +4174,812 @@ msgstr "Nessun risultato." msgid "everybody" msgstr "tutti" -#: mod/settings.php:47 -msgid "Additional features" -msgstr "Funzionalità aggiuntive" - -#: mod/settings.php:53 +#: mod/settings.php:58 msgid "Display" msgstr "Visualizzazione" -#: mod/settings.php:60 mod/settings.php:855 +#: mod/settings.php:65 mod/settings.php:864 msgid "Social Networks" msgstr "Social Networks" -#: mod/settings.php:72 include/nav.php:180 +#: mod/settings.php:79 include/nav.php:180 msgid "Delegations" msgstr "Delegazioni" -#: mod/settings.php:78 +#: mod/settings.php:86 msgid "Connected apps" msgstr "Applicazioni collegate" -#: mod/settings.php:84 mod/uexport.php:85 +#: mod/settings.php:93 mod/uexport.php:85 msgid "Export personal data" msgstr "Esporta dati personali" -#: mod/settings.php:90 +#: mod/settings.php:100 msgid "Remove account" msgstr "Rimuovi account" -#: mod/settings.php:143 +#: mod/settings.php:153 msgid "Missing some important data!" msgstr "Mancano alcuni dati importanti!" -#: mod/settings.php:256 +#: mod/settings.php:266 msgid "Failed to connect with email account using the settings provided." msgstr "Impossibile collegarsi all'account email con i parametri forniti." -#: mod/settings.php:261 +#: mod/settings.php:271 msgid "Email settings updated." msgstr "Impostazioni e-mail aggiornate." -#: mod/settings.php:276 +#: mod/settings.php:286 msgid "Features updated" msgstr "Funzionalità aggiornate" -#: mod/settings.php:343 +#: mod/settings.php:353 msgid "Relocate message has been send to your contacts" msgstr "Il messaggio di trasloco è stato inviato ai tuoi contatti" -#: mod/settings.php:357 include/user.php:39 +#: mod/settings.php:367 include/user.php:39 msgid "Passwords do not match. Password unchanged." msgstr "Le password non corrispondono. Password non cambiata." -#: mod/settings.php:362 +#: mod/settings.php:372 msgid "Empty passwords are not allowed. Password unchanged." msgstr "Le password non possono essere vuote. Password non cambiata." -#: mod/settings.php:370 +#: mod/settings.php:380 msgid "Wrong password." msgstr "Password sbagliata." -#: mod/settings.php:381 +#: mod/settings.php:391 msgid "Password changed." msgstr "Password cambiata." -#: mod/settings.php:383 +#: mod/settings.php:393 msgid "Password update failed. Please try again." msgstr "Aggiornamento password fallito. Prova ancora." -#: mod/settings.php:452 +#: mod/settings.php:462 msgid " Please use a shorter name." msgstr " Usa un nome più corto." -#: mod/settings.php:454 +#: mod/settings.php:464 msgid " Name too short." msgstr " Nome troppo corto." -#: mod/settings.php:463 +#: mod/settings.php:473 msgid "Wrong Password" msgstr "Password Sbagliata" -#: mod/settings.php:468 +#: mod/settings.php:478 msgid " Not valid email." msgstr " Email non valida." -#: mod/settings.php:474 +#: mod/settings.php:484 msgid " Cannot change to that email." msgstr "Non puoi usare quella email." -#: mod/settings.php:530 +#: mod/settings.php:540 msgid "Private forum has no privacy permissions. Using default privacy group." msgstr "Il forum privato non ha permessi di privacy. Uso il gruppo di privacy predefinito." -#: mod/settings.php:534 +#: mod/settings.php:544 msgid "Private forum has no privacy permissions and no default privacy group." msgstr "Il gruppo privato non ha permessi di privacy e nessun gruppo di privacy predefinito." -#: mod/settings.php:573 +#: mod/settings.php:583 msgid "Settings updated." msgstr "Impostazioni aggiornate." -#: mod/settings.php:649 mod/settings.php:675 mod/settings.php:711 +#: mod/settings.php:658 mod/settings.php:684 mod/settings.php:720 msgid "Add application" msgstr "Aggiungi applicazione" -#: mod/settings.php:653 mod/settings.php:679 +#: mod/settings.php:662 mod/settings.php:688 msgid "Consumer Key" msgstr "Consumer Key" -#: mod/settings.php:654 mod/settings.php:680 +#: mod/settings.php:663 mod/settings.php:689 msgid "Consumer Secret" msgstr "Consumer Secret" -#: mod/settings.php:655 mod/settings.php:681 +#: mod/settings.php:664 mod/settings.php:690 msgid "Redirect" msgstr "Redirect" -#: mod/settings.php:656 mod/settings.php:682 +#: mod/settings.php:665 mod/settings.php:691 msgid "Icon url" msgstr "Url icona" -#: mod/settings.php:667 +#: mod/settings.php:676 msgid "You can't edit this application." msgstr "Non puoi modificare questa applicazione." -#: mod/settings.php:710 +#: mod/settings.php:719 msgid "Connected Apps" msgstr "Applicazioni Collegate" -#: mod/settings.php:714 +#: mod/settings.php:723 msgid "Client key starts with" msgstr "Chiave del client inizia con" -#: mod/settings.php:715 +#: mod/settings.php:724 msgid "No name" msgstr "Nessun nome" -#: mod/settings.php:716 +#: mod/settings.php:725 msgid "Remove authorization" msgstr "Rimuovi l'autorizzazione" -#: mod/settings.php:728 +#: mod/settings.php:737 msgid "No Plugin settings configured" msgstr "Nessun plugin ha impostazioni modificabili" -#: mod/settings.php:736 +#: mod/settings.php:745 msgid "Plugin Settings" msgstr "Impostazioni plugin" -#: mod/settings.php:750 -msgid "Off" -msgstr "Spento" - -#: mod/settings.php:750 -msgid "On" -msgstr "Acceso" - -#: mod/settings.php:758 +#: mod/settings.php:767 msgid "Additional Features" msgstr "Funzionalità aggiuntive" -#: mod/settings.php:768 mod/settings.php:772 +#: mod/settings.php:777 mod/settings.php:781 msgid "General Social Media Settings" msgstr "Impostazioni Media Sociali" -#: mod/settings.php:778 +#: mod/settings.php:787 msgid "Disable intelligent shortening" msgstr "Disabilita accorciamento intelligente" -#: mod/settings.php:780 +#: mod/settings.php:789 msgid "" "Normally the system tries to find the best link to add to shortened posts. " "If this option is enabled then every shortened post will always point to the" " original friendica post." msgstr "Normalmente il sistema tenta di trovare il migliore link da aggiungere a un post accorciato. Se questa opzione è abilitata, ogni post accorciato conterrà sempre un link al post originale su Friendica." -#: mod/settings.php:786 +#: mod/settings.php:795 msgid "Automatically follow any GNU Social (OStatus) followers/mentioners" msgstr "Segui automanticamente chiunque da GNU Social (OStatus) ti segua o ti menzioni" -#: mod/settings.php:788 +#: mod/settings.php:797 msgid "" "If you receive a message from an unknown OStatus user, this option decides " "what to do. If it is checked, a new contact will be created for every " "unknown user." msgstr "Se ricevi un messaggio da un utente OStatus sconosciuto, questa opzione decide cosa fare. Se selezionato, un nuovo contatto verrà creato per ogni utente sconosciuto." -#: mod/settings.php:797 +#: mod/settings.php:806 msgid "Your legacy GNU Social account" msgstr "Il tuo vecchio account GNU Social" -#: mod/settings.php:799 +#: mod/settings.php:808 msgid "" "If you enter your old GNU Social/Statusnet account name here (in the format " "user@domain.tld), your contacts will be added automatically. The field will " "be emptied when done." msgstr "Se inserisci il nome del tuo vecchio account GNU Social/Statusnet qui (nel formato utente@dominio.tld), i tuoi contatti verranno automaticamente aggiunti. Il campo verrà svuotato una volta terminato." -#: mod/settings.php:802 +#: mod/settings.php:811 msgid "Repair OStatus subscriptions" msgstr "Ripara le iscrizioni OStatus" -#: mod/settings.php:811 mod/settings.php:812 +#: mod/settings.php:820 mod/settings.php:821 #, php-format msgid "Built-in support for %s connectivity is %s" msgstr "Il supporto integrato per la connettività con %s è %s" -#: mod/settings.php:811 mod/dfrn_request.php:858 +#: mod/settings.php:820 mod/dfrn_request.php:865 #: include/contact_selectors.php:80 msgid "Diaspora" msgstr "Diaspora" -#: mod/settings.php:811 mod/settings.php:812 +#: mod/settings.php:820 mod/settings.php:821 msgid "enabled" msgstr "abilitato" -#: mod/settings.php:811 mod/settings.php:812 +#: mod/settings.php:820 mod/settings.php:821 msgid "disabled" msgstr "disabilitato" -#: mod/settings.php:812 +#: mod/settings.php:821 msgid "GNU Social (OStatus)" msgstr "GNU Social (OStatus)" -#: mod/settings.php:848 +#: mod/settings.php:857 msgid "Email access is disabled on this site." msgstr "L'accesso email è disabilitato su questo sito." -#: mod/settings.php:860 +#: mod/settings.php:869 msgid "Email/Mailbox Setup" msgstr "Impostazioni email" -#: mod/settings.php:861 +#: mod/settings.php:870 msgid "" "If you wish to communicate with email contacts using this service " "(optional), please specify how to connect to your mailbox." msgstr "Se vuoi comunicare con i contatti email usando questo servizio, specifica come collegarti alla tua casella di posta. (opzionale)" -#: mod/settings.php:862 +#: mod/settings.php:871 msgid "Last successful email check:" msgstr "Ultimo controllo email eseguito con successo:" -#: mod/settings.php:864 +#: mod/settings.php:873 msgid "IMAP server name:" msgstr "Nome server IMAP:" -#: mod/settings.php:865 +#: mod/settings.php:874 msgid "IMAP port:" msgstr "Porta IMAP:" -#: mod/settings.php:866 +#: mod/settings.php:875 msgid "Security:" msgstr "Sicurezza:" -#: mod/settings.php:866 mod/settings.php:871 +#: mod/settings.php:875 mod/settings.php:880 msgid "None" msgstr "Nessuna" -#: mod/settings.php:867 +#: mod/settings.php:876 msgid "Email login name:" msgstr "Nome utente email:" -#: mod/settings.php:868 +#: mod/settings.php:877 msgid "Email password:" msgstr "Password email:" -#: mod/settings.php:869 +#: mod/settings.php:878 msgid "Reply-to address:" msgstr "Indirizzo di risposta:" -#: mod/settings.php:870 +#: mod/settings.php:879 msgid "Send public posts to all email contacts:" msgstr "Invia i messaggi pubblici ai contatti email:" -#: mod/settings.php:871 +#: mod/settings.php:880 msgid "Action after import:" msgstr "Azione post importazione:" -#: mod/settings.php:871 +#: mod/settings.php:880 msgid "Mark as seen" msgstr "Segna come letto" -#: mod/settings.php:871 +#: mod/settings.php:880 msgid "Move to folder" msgstr "Sposta nella cartella" -#: mod/settings.php:872 +#: mod/settings.php:881 msgid "Move to folder:" msgstr "Sposta nella cartella:" -#: mod/settings.php:958 +#: mod/settings.php:967 msgid "Display Settings" msgstr "Impostazioni Grafiche" -#: mod/settings.php:964 mod/settings.php:982 +#: mod/settings.php:973 mod/settings.php:991 msgid "Display Theme:" msgstr "Tema:" -#: mod/settings.php:965 +#: mod/settings.php:974 msgid "Mobile Theme:" msgstr "Tema mobile:" -#: mod/settings.php:966 +#: mod/settings.php:975 msgid "Update browser every xx seconds" msgstr "Aggiorna il browser ogni x secondi" -#: mod/settings.php:966 +#: mod/settings.php:975 msgid "Minimum of 10 seconds. Enter -1 to disable it." msgstr "Minimo 10 secondi. Inserisci -1 per disabilitarlo" -#: mod/settings.php:967 +#: mod/settings.php:976 msgid "Number of items to display per page:" msgstr "Numero di elementi da mostrare per pagina:" -#: mod/settings.php:967 mod/settings.php:968 +#: mod/settings.php:976 mod/settings.php:977 msgid "Maximum of 100 items" msgstr "Massimo 100 voci" -#: mod/settings.php:968 +#: mod/settings.php:977 msgid "Number of items to display per page when viewed from mobile device:" msgstr "Numero di voci da visualizzare per pagina quando si utilizza un dispositivo mobile:" -#: mod/settings.php:969 +#: mod/settings.php:978 msgid "Don't show emoticons" msgstr "Non mostrare le emoticons" -#: mod/settings.php:970 +#: mod/settings.php:979 msgid "Calendar" msgstr "Calendario" -#: mod/settings.php:971 +#: mod/settings.php:980 msgid "Beginning of week:" msgstr "Inizio della settimana:" -#: mod/settings.php:972 +#: mod/settings.php:981 msgid "Don't show notices" msgstr "Non mostrare gli avvisi" -#: mod/settings.php:973 +#: mod/settings.php:982 msgid "Infinite scroll" msgstr "Scroll infinito" -#: mod/settings.php:974 +#: mod/settings.php:983 msgid "Automatic updates only at the top of the network page" msgstr "Aggiornamenti automatici solo in cima alla pagina \"rete\"" -#: mod/settings.php:976 view/theme/cleanzero/config.php:82 +#: mod/settings.php:985 view/theme/cleanzero/config.php:82 #: view/theme/dispy/config.php:72 view/theme/quattro/config.php:66 -#: view/theme/diabook/config.php:150 view/theme/clean/config.php:85 -#: view/theme/vier/config.php:109 view/theme/duepuntozero/config.php:61 +#: view/theme/diabook/config.php:150 view/theme/vier/config.php:109 +#: view/theme/duepuntozero/config.php:61 msgid "Theme settings" msgstr "Impostazioni tema" -#: mod/settings.php:1053 +#: mod/settings.php:1062 msgid "User Types" msgstr "Tipi di Utenti" -#: mod/settings.php:1054 +#: mod/settings.php:1063 msgid "Community Types" msgstr "Tipi di Comunità" -#: mod/settings.php:1055 +#: mod/settings.php:1064 msgid "Normal Account Page" msgstr "Pagina Account Normale" -#: mod/settings.php:1056 +#: mod/settings.php:1065 msgid "This account is a normal personal profile" msgstr "Questo account è un normale profilo personale" -#: mod/settings.php:1059 +#: mod/settings.php:1068 msgid "Soapbox Page" msgstr "Pagina Sandbox" -#: mod/settings.php:1060 +#: mod/settings.php:1069 msgid "Automatically approve all connection/friend requests as read-only fans" msgstr "Chi richiede la connessione/amicizia sarà accettato automaticamente come fan che potrà solamente leggere la bacheca" -#: mod/settings.php:1063 +#: mod/settings.php:1072 msgid "Community Forum/Celebrity Account" msgstr "Account Celebrità/Forum comunitario" -#: mod/settings.php:1064 +#: mod/settings.php:1073 msgid "" "Automatically approve all connection/friend requests as read-write fans" msgstr "Chi richiede la connessione/amicizia sarà accettato automaticamente come fan che potrà leggere e scrivere sulla bacheca" -#: mod/settings.php:1067 +#: mod/settings.php:1076 msgid "Automatic Friend Page" msgstr "Pagina con amicizia automatica" -#: mod/settings.php:1068 +#: mod/settings.php:1077 msgid "Automatically approve all connection/friend requests as friends" msgstr "Chi richiede la connessione/amicizia sarà accettato automaticamente come amico" -#: mod/settings.php:1071 +#: mod/settings.php:1080 msgid "Private Forum [Experimental]" msgstr "Forum privato [sperimentale]" -#: mod/settings.php:1072 +#: mod/settings.php:1081 msgid "Private forum - approved members only" msgstr "Forum privato - solo membri approvati" -#: mod/settings.php:1084 +#: mod/settings.php:1093 msgid "OpenID:" msgstr "OpenID:" -#: mod/settings.php:1084 +#: mod/settings.php:1093 msgid "(Optional) Allow this OpenID to login to this account." msgstr "(Opzionale) Consente di loggarti in questo account con questo OpenID" -#: mod/settings.php:1094 +#: mod/settings.php:1103 msgid "Publish your default profile in your local site directory?" msgstr "Pubblica il tuo profilo predefinito nell'elenco locale del sito" -#: mod/settings.php:1100 +#: mod/settings.php:1109 msgid "Publish your default profile in the global social directory?" msgstr "Pubblica il tuo profilo predefinito nell'elenco sociale globale" -#: mod/settings.php:1108 +#: mod/settings.php:1117 msgid "Hide your contact/friend list from viewers of your default profile?" msgstr "Nascondi la lista dei tuoi contatti/amici dai visitatori del tuo profilo predefinito" -#: mod/settings.php:1112 include/acl_selectors.php:331 +#: mod/settings.php:1121 include/acl_selectors.php:331 msgid "Hide your profile details from unknown viewers?" msgstr "Nascondi i dettagli del tuo profilo ai visitatori sconosciuti?" -#: mod/settings.php:1112 +#: mod/settings.php:1121 msgid "" "If enabled, posting public messages to Diaspora and other networks isn't " "possible." msgstr "Se abilitato, l'invio di messaggi pubblici verso Diaspora e altri network non sarà possibile" -#: mod/settings.php:1117 +#: mod/settings.php:1126 msgid "Allow friends to post to your profile page?" msgstr "Permetti agli amici di scrivere sulla tua pagina profilo?" -#: mod/settings.php:1123 +#: mod/settings.php:1132 msgid "Allow friends to tag your posts?" msgstr "Permetti agli amici di taggare i tuoi messaggi?" -#: mod/settings.php:1129 +#: mod/settings.php:1138 msgid "Allow us to suggest you as a potential friend to new members?" msgstr "Ci permetti di suggerirti come potenziale amico ai nuovi membri?" -#: mod/settings.php:1135 +#: mod/settings.php:1144 msgid "Permit unknown people to send you private mail?" msgstr "Permetti a utenti sconosciuti di inviarti messaggi privati?" -#: mod/settings.php:1143 +#: mod/settings.php:1152 msgid "Profile is <strong>not published</strong>." msgstr "Il profilo <strong>non è pubblicato</strong>." -#: mod/settings.php:1151 +#: mod/settings.php:1160 #, php-format msgid "Your Identity Address is <strong>'%s'</strong> or '%s'." msgstr "L'indirizzo della tua identità è <strong>'%s'</strong> or '%s'." -#: mod/settings.php:1158 +#: mod/settings.php:1167 msgid "Automatically expire posts after this many days:" msgstr "Fai scadere i post automaticamente dopo x giorni:" -#: mod/settings.php:1158 +#: mod/settings.php:1167 msgid "If empty, posts will not expire. Expired posts will be deleted" msgstr "Se lasciato vuoto, i messaggi non verranno cancellati." -#: mod/settings.php:1159 +#: mod/settings.php:1168 msgid "Advanced expiration settings" msgstr "Impostazioni avanzate di scandenza" -#: mod/settings.php:1160 +#: mod/settings.php:1169 msgid "Advanced Expiration" msgstr "Scadenza avanzata" -#: mod/settings.php:1161 +#: mod/settings.php:1170 msgid "Expire posts:" msgstr "Fai scadere i post:" -#: mod/settings.php:1162 +#: mod/settings.php:1171 msgid "Expire personal notes:" msgstr "Fai scadere le Note personali:" -#: mod/settings.php:1163 +#: mod/settings.php:1172 msgid "Expire starred posts:" msgstr "Fai scadere i post Speciali:" -#: mod/settings.php:1164 +#: mod/settings.php:1173 msgid "Expire photos:" msgstr "Fai scadere le foto:" -#: mod/settings.php:1165 +#: mod/settings.php:1174 msgid "Only expire posts by others:" msgstr "Fai scadere solo i post degli altri:" -#: mod/settings.php:1193 +#: mod/settings.php:1202 msgid "Account Settings" msgstr "Impostazioni account" -#: mod/settings.php:1201 +#: mod/settings.php:1210 msgid "Password Settings" msgstr "Impostazioni password" -#: mod/settings.php:1202 mod/register.php:274 +#: mod/settings.php:1211 mod/register.php:274 msgid "New Password:" msgstr "Nuova password:" -#: mod/settings.php:1203 mod/register.php:275 +#: mod/settings.php:1212 mod/register.php:275 msgid "Confirm:" msgstr "Conferma:" -#: mod/settings.php:1203 +#: mod/settings.php:1212 msgid "Leave password fields blank unless changing" msgstr "Lascia questi campi in bianco per non effettuare variazioni alla password" -#: mod/settings.php:1204 +#: mod/settings.php:1213 msgid "Current Password:" msgstr "Password Attuale:" -#: mod/settings.php:1204 mod/settings.php:1205 +#: mod/settings.php:1213 mod/settings.php:1214 msgid "Your current password to confirm the changes" msgstr "La tua password attuale per confermare le modifiche" -#: mod/settings.php:1205 +#: mod/settings.php:1214 msgid "Password:" msgstr "Password:" -#: mod/settings.php:1209 +#: mod/settings.php:1218 msgid "Basic Settings" msgstr "Impostazioni base" -#: mod/settings.php:1210 include/identity.php:578 +#: mod/settings.php:1219 include/identity.php:588 msgid "Full Name:" msgstr "Nome completo:" -#: mod/settings.php:1211 +#: mod/settings.php:1220 msgid "Email Address:" msgstr "Indirizzo Email:" -#: mod/settings.php:1212 +#: mod/settings.php:1221 msgid "Your Timezone:" msgstr "Il tuo fuso orario:" -#: mod/settings.php:1213 +#: mod/settings.php:1222 msgid "Your Language:" msgstr "La tua lingua:" -#: mod/settings.php:1213 +#: mod/settings.php:1222 msgid "" "Set the language we use to show you friendica interface and to send you " "emails" msgstr "Imposta la lingua che sarà usata per mostrarti l'interfaccia di Friendica e per inviarti le email" -#: mod/settings.php:1214 +#: mod/settings.php:1223 msgid "Default Post Location:" msgstr "Località predefinita:" -#: mod/settings.php:1215 +#: mod/settings.php:1224 msgid "Use Browser Location:" msgstr "Usa la località rilevata dal browser:" -#: mod/settings.php:1218 +#: mod/settings.php:1227 msgid "Security and Privacy Settings" msgstr "Impostazioni di sicurezza e privacy" -#: mod/settings.php:1220 +#: mod/settings.php:1229 msgid "Maximum Friend Requests/Day:" msgstr "Numero massimo di richieste di amicizia al giorno:" -#: mod/settings.php:1220 mod/settings.php:1250 +#: mod/settings.php:1229 mod/settings.php:1259 msgid "(to prevent spam abuse)" msgstr "(per prevenire lo spam)" -#: mod/settings.php:1221 +#: mod/settings.php:1230 msgid "Default Post Permissions" msgstr "Permessi predefiniti per i messaggi" -#: mod/settings.php:1222 +#: mod/settings.php:1231 msgid "(click to open/close)" msgstr "(clicca per aprire/chiudere)" -#: mod/settings.php:1231 mod/photos.php:1199 mod/photos.php:1584 +#: mod/settings.php:1240 mod/photos.php:1199 mod/photos.php:1584 msgid "Show to Groups" msgstr "Mostra ai gruppi" -#: mod/settings.php:1232 mod/photos.php:1200 mod/photos.php:1585 +#: mod/settings.php:1241 mod/photos.php:1200 mod/photos.php:1585 msgid "Show to Contacts" msgstr "Mostra ai contatti" -#: mod/settings.php:1233 +#: mod/settings.php:1242 msgid "Default Private Post" msgstr "Default Post Privato" -#: mod/settings.php:1234 +#: mod/settings.php:1243 msgid "Default Public Post" msgstr "Default Post Pubblico" -#: mod/settings.php:1238 +#: mod/settings.php:1247 msgid "Default Permissions for New Posts" msgstr "Permessi predefiniti per i nuovi post" -#: mod/settings.php:1250 +#: mod/settings.php:1259 msgid "Maximum private messages per day from unknown people:" msgstr "Numero massimo di messaggi privati da utenti sconosciuti per giorno:" -#: mod/settings.php:1253 +#: mod/settings.php:1262 msgid "Notification Settings" msgstr "Impostazioni notifiche" -#: mod/settings.php:1254 +#: mod/settings.php:1263 msgid "By default post a status message when:" msgstr "Invia un messaggio di stato quando:" -#: mod/settings.php:1255 +#: mod/settings.php:1264 msgid "accepting a friend request" msgstr "accetti una richiesta di amicizia" -#: mod/settings.php:1256 +#: mod/settings.php:1265 msgid "joining a forum/community" msgstr "ti unisci a un forum/comunità" -#: mod/settings.php:1257 +#: mod/settings.php:1266 msgid "making an <em>interesting</em> profile change" msgstr "fai un <em>interessante</em> modifica al profilo" -#: mod/settings.php:1258 +#: mod/settings.php:1267 msgid "Send a notification email when:" msgstr "Invia una mail di notifica quando:" -#: mod/settings.php:1259 +#: mod/settings.php:1268 msgid "You receive an introduction" msgstr "Ricevi una presentazione" -#: mod/settings.php:1260 +#: mod/settings.php:1269 msgid "Your introductions are confirmed" msgstr "Le tue presentazioni sono confermate" -#: mod/settings.php:1261 +#: mod/settings.php:1270 msgid "Someone writes on your profile wall" msgstr "Qualcuno scrive sulla bacheca del tuo profilo" -#: mod/settings.php:1262 +#: mod/settings.php:1271 msgid "Someone writes a followup comment" msgstr "Qualcuno scrive un commento a un tuo messaggio" -#: mod/settings.php:1263 +#: mod/settings.php:1272 msgid "You receive a private message" msgstr "Ricevi un messaggio privato" -#: mod/settings.php:1264 +#: mod/settings.php:1273 msgid "You receive a friend suggestion" msgstr "Hai ricevuto un suggerimento di amicizia" -#: mod/settings.php:1265 +#: mod/settings.php:1274 msgid "You are tagged in a post" msgstr "Sei stato taggato in un post" -#: mod/settings.php:1266 +#: mod/settings.php:1275 msgid "You are poked/prodded/etc. in a post" msgstr "Sei 'toccato'/'spronato'/ecc. in un post" -#: mod/settings.php:1268 +#: mod/settings.php:1277 msgid "Activate desktop notifications" msgstr "Attiva notifiche desktop" -#: mod/settings.php:1268 +#: mod/settings.php:1277 msgid "Show desktop popup on new notifications" msgstr "Mostra un popup di notifica sul desktop all'arrivo di nuove notifiche" -#: mod/settings.php:1270 +#: mod/settings.php:1279 msgid "Text-only notification emails" msgstr "Email di notifica in solo testo" -#: mod/settings.php:1272 +#: mod/settings.php:1281 msgid "Send text only notification emails, without the html part" msgstr "Invia le email di notifica in solo testo, senza la parte in html" -#: mod/settings.php:1274 +#: mod/settings.php:1283 msgid "Advanced Account/Page Type Settings" msgstr "Impostazioni avanzate Account/Tipo di pagina" -#: mod/settings.php:1275 +#: mod/settings.php:1284 msgid "Change the behaviour of this account for special situations" msgstr "Modifica il comportamento di questo account in situazioni speciali" -#: mod/settings.php:1278 +#: mod/settings.php:1287 msgid "Relocate" msgstr "Trasloca" -#: mod/settings.php:1279 +#: mod/settings.php:1288 msgid "" "If you have moved this profile from another server, and some of your " "contacts don't receive your updates, try pushing this button." msgstr "Se hai spostato questo profilo da un'altro server, e alcuni dei tuoi contatti non ricevono i tuoi aggiornamenti, prova a premere questo bottone." -#: mod/settings.php:1280 +#: mod/settings.php:1289 msgid "Resend relocate message to contacts" msgstr "Reinvia il messaggio di trasloco" -#: mod/dfrn_request.php:95 +#: mod/dfrn_request.php:96 msgid "This introduction has already been accepted." msgstr "Questa presentazione è già stata accettata." -#: mod/dfrn_request.php:120 mod/dfrn_request.php:519 +#: mod/dfrn_request.php:119 mod/dfrn_request.php:516 msgid "Profile location is not valid or does not contain profile information." msgstr "L'indirizzo del profilo non è valido o non contiene un profilo." -#: mod/dfrn_request.php:125 mod/dfrn_request.php:524 +#: mod/dfrn_request.php:124 mod/dfrn_request.php:521 msgid "Warning: profile location has no identifiable owner name." msgstr "Attenzione: l'indirizzo del profilo non riporta il nome del proprietario." -#: mod/dfrn_request.php:127 mod/dfrn_request.php:526 +#: mod/dfrn_request.php:126 mod/dfrn_request.php:523 msgid "Warning: profile location has no profile photo." msgstr "Attenzione: l'indirizzo del profilo non ha una foto." -#: mod/dfrn_request.php:130 mod/dfrn_request.php:529 +#: mod/dfrn_request.php:129 mod/dfrn_request.php:526 #, php-format msgid "%d required parameter was not found at the given location" msgid_plural "%d required parameters were not found at the given location" msgstr[0] "%d parametro richiesto non è stato trovato all'indirizzo dato" msgstr[1] "%d parametri richiesti non sono stati trovati all'indirizzo dato" -#: mod/dfrn_request.php:173 +#: mod/dfrn_request.php:172 msgid "Introduction complete." msgstr "Presentazione completa." -#: mod/dfrn_request.php:215 +#: mod/dfrn_request.php:214 msgid "Unrecoverable protocol error." msgstr "Errore di comunicazione." -#: mod/dfrn_request.php:243 +#: mod/dfrn_request.php:242 msgid "Profile unavailable." msgstr "Profilo non disponibile." -#: mod/dfrn_request.php:268 +#: mod/dfrn_request.php:267 #, php-format msgid "%s has received too many connection requests today." msgstr "%s ha ricevuto troppe richieste di connessione per oggi." -#: mod/dfrn_request.php:269 +#: mod/dfrn_request.php:268 msgid "Spam protection measures have been invoked." msgstr "Sono state attivate le misure di protezione contro lo spam." -#: mod/dfrn_request.php:270 +#: mod/dfrn_request.php:269 msgid "Friends are advised to please try again in 24 hours." msgstr "Gli amici sono pregati di riprovare tra 24 ore." -#: mod/dfrn_request.php:332 +#: mod/dfrn_request.php:331 msgid "Invalid locator" msgstr "Invalid locator" -#: mod/dfrn_request.php:341 +#: mod/dfrn_request.php:340 msgid "Invalid email address." msgstr "Indirizzo email non valido." -#: mod/dfrn_request.php:368 +#: mod/dfrn_request.php:367 msgid "This account has not been configured for email. Request failed." msgstr "Questo account non è stato configurato per l'email. Richiesta fallita." -#: mod/dfrn_request.php:464 -msgid "Unable to resolve your name at the provided location." -msgstr "Impossibile risolvere il tuo nome nella posizione indicata." - -#: mod/dfrn_request.php:477 +#: mod/dfrn_request.php:474 msgid "You have already introduced yourself here." msgstr "Ti sei già presentato qui." -#: mod/dfrn_request.php:481 +#: mod/dfrn_request.php:478 #, php-format msgid "Apparently you are already friends with %s." msgstr "Pare che tu e %s siate già amici." -#: mod/dfrn_request.php:502 +#: mod/dfrn_request.php:499 msgid "Invalid profile URL." msgstr "Indirizzo profilo non valido." -#: mod/dfrn_request.php:508 include/follow.php:72 +#: mod/dfrn_request.php:505 include/follow.php:72 msgid "Disallowed profile URL." msgstr "Indirizzo profilo non permesso." -#: mod/dfrn_request.php:599 +#: mod/dfrn_request.php:596 msgid "Your introduction has been sent." msgstr "La tua presentazione è stata inviata." -#: mod/dfrn_request.php:652 +#: mod/dfrn_request.php:636 +msgid "" +"Remote subscription can't be done for your network. Please subscribe " +"directly on your system." +msgstr "" + +#: mod/dfrn_request.php:659 msgid "Please login to confirm introduction." msgstr "Accedi per confermare la presentazione." -#: mod/dfrn_request.php:662 +#: mod/dfrn_request.php:669 msgid "" "Incorrect identity currently logged in. Please login to " "<strong>this</strong> profile." msgstr "Non hai fatto accesso con l'identità corretta. Accedi a <strong>questo</strong> profilo." -#: mod/dfrn_request.php:676 mod/dfrn_request.php:693 +#: mod/dfrn_request.php:683 mod/dfrn_request.php:700 msgid "Confirm" msgstr "Conferma" -#: mod/dfrn_request.php:688 +#: mod/dfrn_request.php:695 msgid "Hide this contact" msgstr "Nascondi questo contatto" -#: mod/dfrn_request.php:691 +#: mod/dfrn_request.php:698 #, php-format msgid "Welcome home %s." msgstr "Bentornato a casa %s." -#: mod/dfrn_request.php:692 +#: mod/dfrn_request.php:699 #, php-format msgid "Please confirm your introduction/connection request to %s." msgstr "Conferma la tua richiesta di connessione con %s." -#: mod/dfrn_request.php:821 +#: mod/dfrn_request.php:828 msgid "" "Please enter your 'Identity Address' from one of the following supported " "communications networks:" msgstr "Inserisci il tuo 'Indirizzo Identità' da uno dei seguenti network supportati:" -#: mod/dfrn_request.php:842 +#: mod/dfrn_request.php:849 #, php-format msgid "" "If you are not yet a member of the free social web, <a " @@ -4981,25 +4987,25 @@ msgid "" "join us today</a>." msgstr "Se non sei un membro del web sociale libero, <a href=\"%s/siteinfo\">segui questo link per trovare un sito Friendica pubblico e unisciti a noi oggi</a>" -#: mod/dfrn_request.php:847 +#: mod/dfrn_request.php:854 msgid "Friend/Connection Request" msgstr "Richieste di amicizia/connessione" -#: mod/dfrn_request.php:848 +#: mod/dfrn_request.php:855 msgid "" "Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, " "testuser@identi.ca" msgstr "Esempi: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca" -#: mod/dfrn_request.php:856 include/contact_selectors.php:76 +#: mod/dfrn_request.php:863 include/contact_selectors.php:76 msgid "Friendica" msgstr "Friendica" -#: mod/dfrn_request.php:857 +#: mod/dfrn_request.php:864 msgid "StatusNet/Federated Social Web" msgstr "StatusNet/Federated Social Web" -#: mod/dfrn_request.php:859 +#: mod/dfrn_request.php:866 #, php-format msgid "" " - please do not use this form. Instead, enter %s into your Diaspora search" @@ -5087,7 +5093,7 @@ msgstr "Scegli un nome utente. Deve cominciare con una lettera. L'indirizzo del msgid "Choose a nickname: " msgstr "Scegli un nome utente: " -#: mod/register.php:280 boot.php:1268 include/nav.php:108 +#: mod/register.php:280 boot.php:1405 include/nav.php:108 msgid "Register" msgstr "Registrati" @@ -5129,11 +5135,11 @@ msgstr "Elementi taggati con: %s" msgid "Search results for: %s" msgstr "Risultato della ricerca per: %s" -#: mod/directory.php:149 include/identity.php:309 include/identity.php:600 +#: mod/directory.php:149 include/identity.php:313 include/identity.php:610 msgid "Status:" msgstr "Stato:" -#: mod/directory.php:151 include/identity.php:311 include/identity.php:611 +#: mod/directory.php:151 include/identity.php:315 include/identity.php:621 msgid "Homepage:" msgstr "Homepage:" @@ -5193,7 +5199,7 @@ msgstr "Aggiungi" msgid "No entries." msgstr "Nessuna voce." -#: mod/common.php:87 +#: mod/common.php:86 msgid "No contacts in common." msgstr "Nessun contatto in comune." @@ -5461,7 +5467,7 @@ msgstr "Esempio: cathy123, Cathy Williams, cathy@example.com" msgid "Since [date]:" msgstr "Dal [data]:" -#: mod/profiles.php:724 include/identity.php:609 +#: mod/profiles.php:724 include/identity.php:619 msgid "Sexual Preference:" msgstr "Preferenze sessuali:" @@ -5469,11 +5475,11 @@ msgstr "Preferenze sessuali:" msgid "Homepage URL:" msgstr "Homepage:" -#: mod/profiles.php:726 include/identity.php:613 +#: mod/profiles.php:726 include/identity.php:623 msgid "Hometown:" msgstr "Paese natale:" -#: mod/profiles.php:727 include/identity.php:617 +#: mod/profiles.php:727 include/identity.php:627 msgid "Political Views:" msgstr "Orientamento politico:" @@ -5489,11 +5495,11 @@ msgstr "Parole chiave visibili a tutti:" msgid "Private Keywords:" msgstr "Parole chiave private:" -#: mod/profiles.php:731 include/identity.php:625 +#: mod/profiles.php:731 include/identity.php:635 msgid "Likes:" msgstr "Mi piace:" -#: mod/profiles.php:732 include/identity.php:627 +#: mod/profiles.php:732 include/identity.php:637 msgid "Dislikes:" msgstr "Non mi piace:" @@ -5563,23 +5569,23 @@ msgstr "Età : " msgid "Edit/Manage Profiles" msgstr "Modifica / Gestisci profili" -#: mod/profiles.php:814 include/identity.php:257 include/identity.php:283 +#: mod/profiles.php:814 include/identity.php:260 include/identity.php:286 msgid "Change profile photo" msgstr "Cambia la foto del profilo" -#: mod/profiles.php:815 include/identity.php:258 +#: mod/profiles.php:815 include/identity.php:261 msgid "Create New Profile" msgstr "Crea un nuovo profilo" -#: mod/profiles.php:826 include/identity.php:268 +#: mod/profiles.php:826 include/identity.php:271 msgid "Profile Image" msgstr "Immagine del Profilo" -#: mod/profiles.php:828 include/identity.php:271 +#: mod/profiles.php:828 include/identity.php:274 msgid "visible to everybody" msgstr "visibile a tutti" -#: mod/profiles.php:829 include/identity.php:272 +#: mod/profiles.php:829 include/identity.php:275 msgid "Edit visibility" msgstr "Modifica visibilità" @@ -5591,55 +5597,55 @@ msgstr "Oggetto non trovato" msgid "Edit post" msgstr "Modifica messaggio" -#: mod/editpost.php:111 include/conversation.php:1185 +#: mod/editpost.php:111 include/conversation.php:1184 msgid "upload photo" msgstr "carica foto" -#: mod/editpost.php:112 include/conversation.php:1186 +#: mod/editpost.php:112 include/conversation.php:1185 msgid "Attach file" msgstr "Allega file" -#: mod/editpost.php:113 include/conversation.php:1187 +#: mod/editpost.php:113 include/conversation.php:1186 msgid "attach file" msgstr "allega file" -#: mod/editpost.php:115 include/conversation.php:1189 +#: mod/editpost.php:115 include/conversation.php:1188 msgid "web link" msgstr "link web" -#: mod/editpost.php:116 include/conversation.php:1190 +#: mod/editpost.php:116 include/conversation.php:1189 msgid "Insert video link" msgstr "Inserire collegamento video" -#: mod/editpost.php:117 include/conversation.php:1191 +#: mod/editpost.php:117 include/conversation.php:1190 msgid "video link" msgstr "link video" -#: mod/editpost.php:118 include/conversation.php:1192 +#: mod/editpost.php:118 include/conversation.php:1191 msgid "Insert audio link" msgstr "Inserisci collegamento audio" -#: mod/editpost.php:119 include/conversation.php:1193 +#: mod/editpost.php:119 include/conversation.php:1192 msgid "audio link" msgstr "link audio" -#: mod/editpost.php:120 include/conversation.php:1194 +#: mod/editpost.php:120 include/conversation.php:1193 msgid "Set your location" msgstr "La tua posizione" -#: mod/editpost.php:121 include/conversation.php:1195 +#: mod/editpost.php:121 include/conversation.php:1194 msgid "set location" msgstr "posizione" -#: mod/editpost.php:122 include/conversation.php:1196 +#: mod/editpost.php:122 include/conversation.php:1195 msgid "Clear browser location" msgstr "Rimuovi la localizzazione data dal browser" -#: mod/editpost.php:123 include/conversation.php:1197 +#: mod/editpost.php:123 include/conversation.php:1196 msgid "clear location" msgstr "canc. pos." -#: mod/editpost.php:125 include/conversation.php:1203 +#: mod/editpost.php:125 include/conversation.php:1202 msgid "Permission settings" msgstr "Impostazioni permessi" @@ -5647,15 +5653,15 @@ msgstr "Impostazioni permessi" msgid "CC: email addresses" msgstr "CC: indirizzi email" -#: mod/editpost.php:134 include/conversation.php:1212 +#: mod/editpost.php:134 include/conversation.php:1211 msgid "Public post" msgstr "Messaggio pubblico" -#: mod/editpost.php:137 include/conversation.php:1199 +#: mod/editpost.php:137 include/conversation.php:1198 msgid "Set title" msgstr "Scegli un titolo" -#: mod/editpost.php:139 include/conversation.php:1201 +#: mod/editpost.php:139 include/conversation.php:1200 msgid "Categories (comma-separated list)" msgstr "Categorie (lista separata da virgola)" @@ -5663,39 +5669,39 @@ msgstr "Categorie (lista separata da virgola)" msgid "Example: bob@example.com, mary@example.com" msgstr "Esempio: bob@example.com, mary@example.com" -#: mod/friendica.php:59 +#: mod/friendica.php:70 msgid "This is Friendica, version" msgstr "Questo è Friendica, versione" -#: mod/friendica.php:60 +#: mod/friendica.php:71 msgid "running at web location" msgstr "in esecuzione all'indirizzo web" -#: mod/friendica.php:62 +#: mod/friendica.php:73 msgid "" "Please visit <a href=\"http://friendica.com\">Friendica.com</a> to learn " "more about the Friendica project." msgstr "Visita <a href=\"http://friendica.com\">Friendica.com</a> per saperne di più sul progetto Friendica." -#: mod/friendica.php:64 +#: mod/friendica.php:75 msgid "Bug reports and issues: please visit" msgstr "Segnalazioni di bug e problemi: visita" -#: mod/friendica.php:64 +#: mod/friendica.php:75 msgid "the bugtracker at github" msgstr "il bugtracker su github" -#: mod/friendica.php:65 +#: mod/friendica.php:76 msgid "" "Suggestions, praise, donations, etc. - please email \"Info\" at Friendica - " "dot com" msgstr "Suggerimenti, lodi, donazioni, ecc - e-mail a \"Info\" at Friendica punto com" -#: mod/friendica.php:79 +#: mod/friendica.php:90 msgid "Installed plugins/addons/apps:" msgstr "Plugin/addon/applicazioni instalate" -#: mod/friendica.php:92 +#: mod/friendica.php:103 msgid "No installed plugins/addons/apps" msgstr "Nessun plugin/addons/applicazione installata" @@ -5725,7 +5731,7 @@ msgstr "Informazioni remote sulla privacy non disponibili." msgid "Visible to:" msgstr "Visibile a:" -#: mod/notes.php:46 include/identity.php:721 +#: mod/notes.php:46 include/identity.php:730 msgid "Personal Notes" msgstr "Note personali" @@ -5783,8 +5789,8 @@ msgid "Make this post private" msgstr "Rendi questo post privato" #: mod/repair_ostatus.php:14 -msgid "Resubsribing to OStatus contacts" -msgstr "Reiscrizione a contatti OStatus" +msgid "Resubscribing to OStatus contacts" +msgstr "" #: mod/repair_ostatus.php:30 msgid "Error" @@ -5836,7 +5842,7 @@ msgstr "Visita %s per una lista di siti pubblici a cui puoi iscriverti. I membri msgid "" "To accept this invitation, please visit and register at %s or any other " "public Friendica website." -msgstr "Per accettare questo invito, visita e resitrati su %s o su un'altro sito web Friendica aperto al pubblico." +msgstr "Per accettare questo invito, visita e registrati su %s o su un'altro sito web Friendica aperto al pubblico." #: mod/invite.php:123 #, php-format @@ -5865,7 +5871,7 @@ msgstr "Inserisci gli indirizzi email, uno per riga:" msgid "" "You are cordially invited to join me and other close friends on Friendica - " "and help us to create a better social web." -msgstr "Sei cordialmente invitato a unirti a me ed ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore." +msgstr "Sei cordialmente invitato/a ad unirti a me e ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore." #: mod/invite.php:137 msgid "You will need to supply this invitation code: $invite_code" @@ -5882,7 +5888,7 @@ msgid "" "important, please visit http://friendica.com" msgstr "Per maggiori informazioni sul progetto Friendica e perchè pensiamo sia importante, visita http://friendica.com" -#: mod/photos.php:99 include/identity.php:696 +#: mod/photos.php:99 include/identity.php:705 msgid "Photo Albums" msgstr "Album foto" @@ -6053,12 +6059,12 @@ msgstr "Foto privata" msgid "Public photo" msgstr "Foto pubblica" -#: mod/photos.php:1609 include/conversation.php:1183 +#: mod/photos.php:1609 include/conversation.php:1182 msgid "Share" msgstr "Condividi" #: mod/photos.php:1648 include/conversation.php:509 -#: include/conversation.php:1414 +#: include/conversation.php:1413 msgid "Attending" msgid_plural "Attending" msgstr[0] "Partecipa" @@ -6132,60 +6138,60 @@ msgstr "Oggetto non disponibile." msgid "Item was not found." msgstr "Oggetto non trovato." -#: boot.php:783 +#: boot.php:868 msgid "Delete this item?" msgstr "Cancellare questo elemento?" -#: boot.php:786 +#: boot.php:871 msgid "show fewer" msgstr "mostra di meno" -#: boot.php:1160 +#: boot.php:1292 #, php-format msgid "Update %s failed. See error logs." msgstr "aggiornamento %s fallito. Guarda i log di errore." -#: boot.php:1267 +#: boot.php:1404 msgid "Create a New Account" msgstr "Crea un nuovo account" -#: boot.php:1292 include/nav.php:72 +#: boot.php:1429 include/nav.php:72 msgid "Logout" msgstr "Esci" -#: boot.php:1295 +#: boot.php:1432 msgid "Nickname or Email address: " msgstr "Nome utente o indirizzo email: " -#: boot.php:1296 +#: boot.php:1433 msgid "Password: " msgstr "Password: " -#: boot.php:1297 +#: boot.php:1434 msgid "Remember me" msgstr "Ricordati di me" -#: boot.php:1300 +#: boot.php:1437 msgid "Or login using OpenID: " msgstr "O entra con OpenID:" -#: boot.php:1306 +#: boot.php:1443 msgid "Forgot your password?" msgstr "Hai dimenticato la password?" -#: boot.php:1309 +#: boot.php:1446 msgid "Website Terms of Service" msgstr "Condizioni di servizio del sito web " -#: boot.php:1310 +#: boot.php:1447 msgid "terms of service" msgstr "condizioni del servizio" -#: boot.php:1312 +#: boot.php:1449 msgid "Website Privacy Policy" msgstr "Politiche di privacy del sito" -#: boot.php:1313 +#: boot.php:1450 msgid "privacy policy" msgstr "politiche di privacy" @@ -6246,25 +6252,25 @@ msgid "" "[pre]%s[/pre]" msgstr "Il messaggio di errore è\n[pre]%s[/pre]" -#: include/dbstructure.php:151 +#: include/dbstructure.php:153 msgid "Errors encountered creating database tables." msgstr "La creazione delle tabelle del database ha generato errori." -#: include/dbstructure.php:209 +#: include/dbstructure.php:230 msgid "Errors encountered performing database changes." msgstr "Riscontrati errori applicando le modifiche al database." -#: include/auth.php:38 +#: include/auth.php:44 msgid "Logged out." msgstr "Uscita effettuata." -#: include/auth.php:128 include/user.php:75 +#: include/auth.php:134 include/user.php:75 msgid "" "We encountered a problem while logging in with the OpenID you provided. " "Please check the correct spelling of the ID." msgstr "Abbiamo incontrato un problema mentre contattavamo il server OpenID che ci hai fornito. Controlla di averlo scritto giusto." -#: include/auth.php:128 include/user.php:75 +#: include/auth.php:134 include/user.php:75 msgid "The error message was:" msgstr "Il messaggio riportato era:" @@ -6321,7 +6327,7 @@ msgstr "Reti" msgid "All Networks" msgstr "Tutte le Reti" -#: include/contact_widgets.php:141 include/features.php:97 +#: include/contact_widgets.php:141 include/features.php:102 msgid "Saved Folders" msgstr "Cartelle Salvate" @@ -6340,194 +6346,194 @@ msgid_plural "%d contacts in common" msgstr[0] "%d contatto in comune" msgstr[1] "%d contatti in comune" -#: include/features.php:58 +#: include/features.php:63 msgid "General Features" msgstr "Funzionalità generali" -#: include/features.php:60 +#: include/features.php:65 msgid "Multiple Profiles" msgstr "Profili multipli" -#: include/features.php:60 +#: include/features.php:65 msgid "Ability to create multiple profiles" msgstr "Possibilità di creare profili multipli" -#: include/features.php:61 +#: include/features.php:66 msgid "Photo Location" msgstr "Località Foto" -#: include/features.php:61 +#: include/features.php:66 msgid "" "Photo metadata is normally stripped. This extracts the location (if present)" " prior to stripping metadata and links it to a map." msgstr "I metadati delle foto vengono rimossi. Questa opzione estrae la località (se presenta) prima di rimuovere i metadati e la collega a una mappa." -#: include/features.php:66 +#: include/features.php:71 msgid "Post Composition Features" msgstr "Funzionalità di composizione dei post" -#: include/features.php:67 +#: include/features.php:72 msgid "Richtext Editor" msgstr "Editor visuale" -#: include/features.php:67 +#: include/features.php:72 msgid "Enable richtext editor" msgstr "Abilita l'editor visuale" -#: include/features.php:68 +#: include/features.php:73 msgid "Post Preview" msgstr "Anteprima dei post" -#: include/features.php:68 +#: include/features.php:73 msgid "Allow previewing posts and comments before publishing them" msgstr "Permetti di avere un'anteprima di messaggi e commenti prima di pubblicarli" -#: include/features.php:69 +#: include/features.php:74 msgid "Auto-mention Forums" msgstr "Auto-cita i Forum" -#: include/features.php:69 +#: include/features.php:74 msgid "" "Add/remove mention when a fourm page is selected/deselected in ACL window." msgstr "Aggiunge o rimuove una citazione quando un forum è selezionato o deselezionato nella finestra dei permessi." -#: include/features.php:74 +#: include/features.php:79 msgid "Network Sidebar Widgets" msgstr "Widget della barra laterale nella pagina Rete" -#: include/features.php:75 +#: include/features.php:80 msgid "Search by Date" msgstr "Cerca per data" -#: include/features.php:75 +#: include/features.php:80 msgid "Ability to select posts by date ranges" msgstr "Permette di filtrare i post per data" -#: include/features.php:76 include/features.php:106 +#: include/features.php:81 include/features.php:111 msgid "List Forums" msgstr "Elenco forum" -#: include/features.php:76 +#: include/features.php:81 msgid "Enable widget to display the forums your are connected with" msgstr "Abilita il widget che mostra i forum ai quali sei connesso" -#: include/features.php:77 +#: include/features.php:82 msgid "Group Filter" msgstr "Filtra gruppi" -#: include/features.php:77 +#: include/features.php:82 msgid "Enable widget to display Network posts only from selected group" msgstr "Abilita il widget per filtrare i post solo per il gruppo selezionato" -#: include/features.php:78 +#: include/features.php:83 msgid "Network Filter" msgstr "Filtro reti" -#: include/features.php:78 +#: include/features.php:83 msgid "Enable widget to display Network posts only from selected network" msgstr "Abilita il widget per mostare i post solo per la rete selezionata" -#: include/features.php:79 +#: include/features.php:84 msgid "Save search terms for re-use" msgstr "Salva i termini cercati per riutilizzarli" -#: include/features.php:84 +#: include/features.php:89 msgid "Network Tabs" msgstr "Schede pagina Rete" -#: include/features.php:85 +#: include/features.php:90 msgid "Network Personal Tab" msgstr "Scheda Personali" -#: include/features.php:85 +#: include/features.php:90 msgid "Enable tab to display only Network posts that you've interacted on" msgstr "Abilita la scheda per mostrare solo i post a cui hai partecipato" -#: include/features.php:86 +#: include/features.php:91 msgid "Network New Tab" msgstr "Scheda Nuovi" -#: include/features.php:86 +#: include/features.php:91 msgid "Enable tab to display only new Network posts (from the last 12 hours)" msgstr "Abilita la scheda per mostrare solo i post nuovi (nelle ultime 12 ore)" -#: include/features.php:87 +#: include/features.php:92 msgid "Network Shared Links Tab" msgstr "Scheda Link Condivisi" -#: include/features.php:87 +#: include/features.php:92 msgid "Enable tab to display only Network posts with links in them" msgstr "Abilita la scheda per mostrare solo i post che contengono link" -#: include/features.php:92 +#: include/features.php:97 msgid "Post/Comment Tools" msgstr "Strumenti per messaggi/commenti" -#: include/features.php:93 +#: include/features.php:98 msgid "Multiple Deletion" msgstr "Eliminazione multipla" -#: include/features.php:93 +#: include/features.php:98 msgid "Select and delete multiple posts/comments at once" msgstr "Seleziona ed elimina vari messagi e commenti in una volta sola" -#: include/features.php:94 +#: include/features.php:99 msgid "Edit Sent Posts" msgstr "Modifica i post inviati" -#: include/features.php:94 +#: include/features.php:99 msgid "Edit and correct posts and comments after sending" msgstr "Modifica e correggi messaggi e commenti dopo averli inviati" -#: include/features.php:95 +#: include/features.php:100 msgid "Tagging" msgstr "Aggiunta tag" -#: include/features.php:95 +#: include/features.php:100 msgid "Ability to tag existing posts" msgstr "Permette di aggiungere tag ai post già esistenti" -#: include/features.php:96 +#: include/features.php:101 msgid "Post Categories" msgstr "Cateorie post" -#: include/features.php:96 +#: include/features.php:101 msgid "Add categories to your posts" msgstr "Aggiungi categorie ai tuoi post" -#: include/features.php:97 +#: include/features.php:102 msgid "Ability to file posts under folders" msgstr "Permette di archiviare i post in cartelle" -#: include/features.php:98 +#: include/features.php:103 msgid "Dislike Posts" msgstr "Non mi piace" -#: include/features.php:98 +#: include/features.php:103 msgid "Ability to dislike posts/comments" msgstr "Permetti di inviare \"non mi piace\" ai messaggi" -#: include/features.php:99 +#: include/features.php:104 msgid "Star Posts" msgstr "Post preferiti" -#: include/features.php:99 +#: include/features.php:104 msgid "Ability to mark special posts with a star indicator" msgstr "Permette di segnare i post preferiti con una stella" -#: include/features.php:100 +#: include/features.php:105 msgid "Mute Post Notifications" msgstr "Silenzia le notifiche di nuovi post" -#: include/features.php:100 +#: include/features.php:105 msgid "Ability to mute notifications for a thread" msgstr "Permette di silenziare le notifiche di nuovi post in una discussione" -#: include/features.php:105 +#: include/features.php:110 msgid "Advanced Profile Settings" msgstr "Impostazioni Avanzate Profilo" -#: include/features.php:106 +#: include/features.php:111 msgid "Show visitors public community forums at the Advanced Profile Page" msgstr "Mostra ai visitatori i forum nella pagina Profilo Avanzato" @@ -6686,149 +6692,181 @@ msgstr "secondi" msgid "%1$d %2$s ago" msgstr "%1$d %2$s fa" -#: include/datetime.php:474 include/items.php:2470 +#: include/datetime.php:474 include/items.php:2500 #, php-format msgid "%s's birthday" msgstr "Compleanno di %s" -#: include/datetime.php:475 include/items.php:2471 +#: include/datetime.php:475 include/items.php:2501 #, php-format msgid "Happy Birthday %s" msgstr "Buon compleanno %s" -#: include/identity.php:43 +#: include/identity.php:42 msgid "Requested account is not available." msgstr "L'account richiesto non è disponibile." -#: include/identity.php:96 include/identity.php:281 include/identity.php:652 +#: include/identity.php:95 include/identity.php:284 include/identity.php:662 msgid "Edit profile" msgstr "Modifica il profilo" -#: include/identity.php:241 +#: include/identity.php:244 msgid "Atom feed" msgstr "Feed Atom" -#: include/identity.php:246 +#: include/identity.php:249 msgid "Message" msgstr "Messaggio" -#: include/identity.php:252 include/nav.php:185 +#: include/identity.php:255 include/nav.php:185 msgid "Profiles" msgstr "Profili" -#: include/identity.php:252 +#: include/identity.php:255 msgid "Manage/edit profiles" msgstr "Gestisci/modifica i profili" -#: include/identity.php:412 include/identity.php:498 +#: include/identity.php:425 include/identity.php:509 msgid "g A l F d" msgstr "g A l d F" -#: include/identity.php:413 include/identity.php:499 +#: include/identity.php:426 include/identity.php:510 msgid "F d" msgstr "d F" -#: include/identity.php:458 include/identity.php:545 +#: include/identity.php:471 include/identity.php:556 msgid "[today]" msgstr "[oggi]" -#: include/identity.php:470 +#: include/identity.php:483 msgid "Birthday Reminders" msgstr "Promemoria compleanni" -#: include/identity.php:471 +#: include/identity.php:484 msgid "Birthdays this week:" msgstr "Compleanni questa settimana:" -#: include/identity.php:532 +#: include/identity.php:543 msgid "[No description]" msgstr "[Nessuna descrizione]" -#: include/identity.php:556 +#: include/identity.php:567 msgid "Event Reminders" msgstr "Promemoria" -#: include/identity.php:557 +#: include/identity.php:568 msgid "Events this week:" msgstr "Eventi di questa settimana:" -#: include/identity.php:585 +#: include/identity.php:595 msgid "j F, Y" msgstr "j F Y" -#: include/identity.php:586 +#: include/identity.php:596 msgid "j F" msgstr "j F" -#: include/identity.php:593 +#: include/identity.php:603 msgid "Birthday:" msgstr "Compleanno:" -#: include/identity.php:597 +#: include/identity.php:607 msgid "Age:" msgstr "Età:" -#: include/identity.php:606 +#: include/identity.php:616 #, php-format msgid "for %1$d %2$s" msgstr "per %1$d %2$s" -#: include/identity.php:619 +#: include/identity.php:629 msgid "Religion:" msgstr "Religione:" -#: include/identity.php:623 +#: include/identity.php:633 msgid "Hobbies/Interests:" msgstr "Hobby/Interessi:" -#: include/identity.php:630 +#: include/identity.php:640 msgid "Contact information and Social Networks:" msgstr "Informazioni su contatti e social network:" -#: include/identity.php:632 +#: include/identity.php:642 msgid "Musical interests:" msgstr "Interessi musicali:" -#: include/identity.php:634 +#: include/identity.php:644 msgid "Books, literature:" msgstr "Libri, letteratura:" -#: include/identity.php:636 +#: include/identity.php:646 msgid "Television:" msgstr "Televisione:" -#: include/identity.php:638 +#: include/identity.php:648 msgid "Film/dance/culture/entertainment:" msgstr "Film/danza/cultura/intrattenimento:" -#: include/identity.php:640 +#: include/identity.php:650 msgid "Love/Romance:" msgstr "Amore:" -#: include/identity.php:642 +#: include/identity.php:652 msgid "Work/employment:" msgstr "Lavoro:" -#: include/identity.php:644 +#: include/identity.php:654 msgid "School/education:" msgstr "Scuola:" -#: include/identity.php:648 +#: include/identity.php:658 msgid "Forums:" msgstr "Forum:" -#: include/identity.php:701 include/identity.php:704 include/nav.php:78 +#: include/identity.php:710 include/identity.php:713 include/nav.php:78 msgid "Videos" msgstr "Video" -#: include/identity.php:716 include/nav.php:140 +#: include/identity.php:725 include/nav.php:140 msgid "Events and Calendar" msgstr "Eventi e calendario" -#: include/identity.php:724 +#: include/identity.php:733 msgid "Only You Can See This" msgstr "Solo tu puoi vedere questo" +#: include/like.php:167 include/conversation.php:122 +#: include/conversation.php:258 include/text.php:1998 +#: view/theme/diabook/theme.php:463 +msgid "event" +msgstr "l'evento" + +#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2185 +#: view/theme/diabook/theme.php:480 +#, php-format +msgid "%1$s likes %2$s's %3$s" +msgstr "A %1$s piace %3$s di %2$s" + +#: include/like.php:186 include/conversation.php:144 +#, php-format +msgid "%1$s doesn't like %2$s's %3$s" +msgstr "A %1$s non piace %3$s di %2$s" + +#: include/like.php:188 +#, php-format +msgid "%1$s is attending %2$s's %3$s" +msgstr "%1$s parteciperà a %3$s di %2$s" + +#: include/like.php:190 +#, php-format +msgid "%1$s is not attending %2$s's %3$s" +msgstr "%1$s non parteciperà a %3$s di %2$s" + +#: include/like.php:192 +#, php-format +msgid "%1$s may attend %2$s's %3$s" +msgstr "%1$s forse parteciperà a %3$s di %2$s" + #: include/acl_selectors.php:325 msgid "Post to Email" msgstr "Invia a email" @@ -6852,6 +6890,10 @@ msgstr "mostra" msgid "don't show" msgstr "non mostrare" +#: include/acl_selectors.php:348 +msgid "Close" +msgstr "Chiudi" + #: include/message.php:15 include/message.php:173 msgid "[no subject]" msgstr "[nessun oggetto]" @@ -6860,31 +6902,31 @@ msgstr "[nessun oggetto]" msgid "stopped following" msgstr "tolto dai seguiti" -#: include/Contact.php:361 include/conversation.php:911 +#: include/Contact.php:337 include/conversation.php:911 msgid "View Status" msgstr "Visualizza stato" -#: include/Contact.php:363 include/conversation.php:913 +#: include/Contact.php:339 include/conversation.php:913 msgid "View Photos" msgstr "Visualizza foto" -#: include/Contact.php:364 include/conversation.php:914 +#: include/Contact.php:340 include/conversation.php:914 msgid "Network Posts" msgstr "Post della Rete" -#: include/Contact.php:365 include/conversation.php:915 +#: include/Contact.php:341 include/conversation.php:915 msgid "Edit Contact" msgstr "Modifica contatto" -#: include/Contact.php:366 +#: include/Contact.php:342 msgid "Drop Contact" msgstr "Rimuovi contatto" -#: include/Contact.php:367 include/conversation.php:916 +#: include/Contact.php:343 include/conversation.php:916 msgid "Send PM" msgstr "Invia messaggio privato" -#: include/Contact.php:368 include/conversation.php:920 +#: include/Contact.php:344 include/conversation.php:920 msgid "Poke" msgstr "Stuzzica" @@ -6947,153 +6989,153 @@ msgstr "Cancella elementi selezionati" msgid "Follow Thread" msgstr "Segui la discussione" -#: include/conversation.php:1035 +#: include/conversation.php:1034 #, php-format msgid "%s likes this." msgstr "Piace a %s." -#: include/conversation.php:1038 +#: include/conversation.php:1037 #, php-format msgid "%s doesn't like this." msgstr "Non piace a %s." -#: include/conversation.php:1041 +#: include/conversation.php:1040 #, php-format msgid "%s attends." msgstr "%s partecipa." -#: include/conversation.php:1044 +#: include/conversation.php:1043 #, php-format msgid "%s doesn't attend." msgstr "%s non partecipa." -#: include/conversation.php:1047 +#: include/conversation.php:1046 #, php-format msgid "%s attends maybe." msgstr "%s forse partecipa." -#: include/conversation.php:1057 +#: include/conversation.php:1056 msgid "and" msgstr "e" -#: include/conversation.php:1063 +#: include/conversation.php:1062 #, php-format msgid ", and %d other people" msgstr "e altre %d persone" -#: include/conversation.php:1072 +#: include/conversation.php:1071 #, php-format msgid "<span %1$s>%2$d people</span> like this" msgstr "Piace a <span %1$s>%2$d persone</span>." -#: include/conversation.php:1073 +#: include/conversation.php:1072 #, php-format msgid "%s like this." msgstr "a %s piace." -#: include/conversation.php:1076 +#: include/conversation.php:1075 #, php-format msgid "<span %1$s>%2$d people</span> don't like this" msgstr "Non piace a <span %1$s>%2$d persone</span>." -#: include/conversation.php:1077 +#: include/conversation.php:1076 #, php-format msgid "%s don't like this." msgstr "a %s non piace." -#: include/conversation.php:1080 +#: include/conversation.php:1079 #, php-format msgid "<span %1$s>%2$d people</span> attend" msgstr "<span %1$s>%2$d persone</span> partecipano" -#: include/conversation.php:1081 +#: include/conversation.php:1080 #, php-format msgid "%s attend." msgstr "%s partecipa." -#: include/conversation.php:1084 +#: include/conversation.php:1083 #, php-format msgid "<span %1$s>%2$d people</span> don't attend" msgstr "<span %1$s>%2$d persone</span> non partecipano" -#: include/conversation.php:1085 +#: include/conversation.php:1084 #, php-format msgid "%s don't attend." msgstr "%s non partecipa." -#: include/conversation.php:1088 +#: include/conversation.php:1087 #, php-format msgid "<span %1$s>%2$d people</span> anttend maybe" msgstr "<span %1$s>%2$d persone</span> forse partecipano" -#: include/conversation.php:1089 +#: include/conversation.php:1088 #, php-format msgid "%s anttend maybe." msgstr "%s forse partecipano." -#: include/conversation.php:1128 include/conversation.php:1146 +#: include/conversation.php:1127 include/conversation.php:1145 msgid "Visible to <strong>everybody</strong>" msgstr "Visibile a <strong>tutti</strong>" -#: include/conversation.php:1130 include/conversation.php:1148 +#: include/conversation.php:1129 include/conversation.php:1147 msgid "Please enter a video link/URL:" msgstr "Inserisci un collegamento video / URL:" -#: include/conversation.php:1131 include/conversation.php:1149 +#: include/conversation.php:1130 include/conversation.php:1148 msgid "Please enter an audio link/URL:" msgstr "Inserisci un collegamento audio / URL:" -#: include/conversation.php:1132 include/conversation.php:1150 +#: include/conversation.php:1131 include/conversation.php:1149 msgid "Tag term:" msgstr "Tag:" -#: include/conversation.php:1134 include/conversation.php:1152 +#: include/conversation.php:1133 include/conversation.php:1151 msgid "Where are you right now?" msgstr "Dove sei ora?" -#: include/conversation.php:1135 +#: include/conversation.php:1134 msgid "Delete item(s)?" msgstr "Cancellare questo elemento/i?" -#: include/conversation.php:1204 +#: include/conversation.php:1203 msgid "permissions" msgstr "permessi" -#: include/conversation.php:1227 +#: include/conversation.php:1226 msgid "Post to Groups" msgstr "Invia ai Gruppi" -#: include/conversation.php:1228 +#: include/conversation.php:1227 msgid "Post to Contacts" msgstr "Invia ai Contatti" -#: include/conversation.php:1229 +#: include/conversation.php:1228 msgid "Private post" msgstr "Post privato" -#: include/conversation.php:1386 +#: include/conversation.php:1385 msgid "View all" msgstr "Mostra tutto" -#: include/conversation.php:1408 +#: include/conversation.php:1407 msgid "Like" msgid_plural "Likes" msgstr[0] "Mi piace" msgstr[1] "Mi piace" -#: include/conversation.php:1411 +#: include/conversation.php:1410 msgid "Dislike" msgid_plural "Dislikes" msgstr[0] "Non mi piace" msgstr[1] "Non mi piace" -#: include/conversation.php:1417 +#: include/conversation.php:1416 msgid "Not Attending" msgid_plural "Not Attending" msgstr[0] "Non partecipa" msgstr[1] "Non partecipano" -#: include/conversation.php:1420 include/profile_selectors.php:6 +#: include/conversation.php:1419 include/profile_selectors.php:6 msgid "Undecided" msgid_plural "Undecided" msgstr[0] "Indeciso" @@ -7295,67 +7337,59 @@ msgstr "rilassato" msgid "surprised" msgstr "sorpreso" -#: include/text.php:1497 +#: include/text.php:1504 msgid "bytes" msgstr "bytes" -#: include/text.php:1529 include/text.php:1541 +#: include/text.php:1536 include/text.php:1548 msgid "Click to open/close" msgstr "Clicca per aprire/chiudere" -#: include/text.php:1715 +#: include/text.php:1722 msgid "View on separate page" msgstr "Vedi in una pagina separata" -#: include/text.php:1716 +#: include/text.php:1723 msgid "view on separate page" msgstr "vedi in una pagina separata" -#: include/text.php:1995 +#: include/text.php:2002 msgid "activity" msgstr "attività" -#: include/text.php:1998 +#: include/text.php:2005 msgid "post" msgstr "messaggio" -#: include/text.php:2166 +#: include/text.php:2173 msgid "Item filed" msgstr "Messaggio salvato" -#: include/bbcode.php:483 include/bbcode.php:1143 include/bbcode.php:1144 +#: include/bbcode.php:482 include/bbcode.php:1157 include/bbcode.php:1158 msgid "Image/photo" msgstr "Immagine/foto" -#: include/bbcode.php:581 +#: include/bbcode.php:595 #, php-format msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s" msgstr "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s" -#: include/bbcode.php:615 +#: include/bbcode.php:629 #, php-format msgid "" "<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a " "href=\"%s\" target=\"_blank\">post</a>" msgstr "<span><a href=\"%s\" target=\"_blank\">%s</a> ha scritto il seguente <a href=\"%s\" target=\"_blank\">messaggio</a>" -#: include/bbcode.php:1103 include/bbcode.php:1123 +#: include/bbcode.php:1117 include/bbcode.php:1137 msgid "$1 wrote:" msgstr "$1 ha scritto:" -#: include/bbcode.php:1152 include/bbcode.php:1153 +#: include/bbcode.php:1166 include/bbcode.php:1167 msgid "Encrypted content" msgstr "Contenuto criptato" -#: include/notifier.php:843 include/delivery.php:458 -msgid "(no subject)" -msgstr "(nessun oggetto)" - -#: include/notifier.php:853 include/delivery.php:469 include/enotify.php:37 -msgid "noreply" -msgstr "nessuna risposta" - -#: include/dba_pdo.php:72 include/dba.php:56 +#: include/dba_pdo.php:72 include/dba.php:55 #, php-format msgid "Cannot locate DNS info for database server '%s'" msgstr "Non trovo le informazioni DNS per il database server '%s'" @@ -7400,6 +7434,10 @@ msgstr "Ostatus" msgid "RSS/Atom" msgstr "RSS / Atom" +#: include/contact_selectors.php:81 +msgid "Facebook" +msgstr "Facebook" + #: include/contact_selectors.php:82 msgid "Zot!" msgstr "Zot!" @@ -7444,7 +7482,7 @@ msgstr "App.net" msgid "Redmatrix" msgstr "Redmatrix" -#: include/Scrape.php:610 +#: include/Scrape.php:624 msgid " on Last.fm" msgstr "su Last.fm" @@ -7620,46 +7658,21 @@ msgstr "Navigazione" msgid "Site map" msgstr "Mappa del sito" -#: include/api.php:343 include/api.php:354 include/api.php:463 -#: include/api.php:1182 include/api.php:1184 -msgid "User not found." -msgstr "Utente non trovato." - -#: include/api.php:830 +#: include/api.php:878 #, php-format msgid "Daily posting limit of %d posts reached. The post was rejected." msgstr "Limite giornaliero di %d messaggi raggiunto. Il messaggio è stato rifiutato" -#: include/api.php:849 +#: include/api.php:897 #, php-format msgid "Weekly posting limit of %d posts reached. The post was rejected." msgstr "Limite settimanale di %d messaggi raggiunto. Il messaggio è stato rifiutato" -#: include/api.php:868 +#: include/api.php:916 #, php-format msgid "Monthly posting limit of %d posts reached. The post was rejected." msgstr "Limite mensile di %d messaggi raggiunto. Il messaggio è stato rifiutato" -#: include/api.php:1391 -msgid "There is no status with this id." -msgstr "Non c'è nessuno status con questo id." - -#: include/api.php:1465 -msgid "There is no conversation with this id." -msgstr "Non c'è nessuna conversazione con questo id" - -#: include/api.php:1744 -msgid "Invalid item." -msgstr "Elemento non valido." - -#: include/api.php:1754 -msgid "Invalid action. " -msgstr "Azione non valida." - -#: include/api.php:1762 -msgid "DB error" -msgstr "Errore database" - #: include/user.php:48 msgid "An invitation is required." msgstr "E' richiesto un invito." @@ -7722,8 +7735,7 @@ msgstr "ERRORE GRAVE: La generazione delle chiavi di sicurezza è fallita." msgid "An error occurred during registration. Please try again." msgstr "C'è stato un errore durante la registrazione. Prova ancora." -#: include/user.php:256 view/theme/clean/config.php:56 -#: view/theme/duepuntozero/config.php:44 +#: include/user.php:256 view/theme/duepuntozero/config.php:44 msgid "default" msgstr "default" @@ -7774,19 +7786,27 @@ msgid "" "\t\tThank you and welcome to %2$s." msgstr "\nI dettagli del tuo utente sono:\n Indirizzo del sito: %3$s\n Nome utente: %1$s\n Password: %5$s\n\nPuoi cambiare la tua password dalla pagina delle impostazioni del tuo account dopo esserti autenticato.\n\nPer favore, prenditi qualche momento per esaminare tutte le impostazioni presenti.\n\nPotresti voler aggiungere qualche informazione di base al tuo profilo predefinito (nella pagina \"Profili\"), così che le altre persone possano trovarti più facilmente.\n\nTi raccomandiamo di inserire il tuo nome completo, aggiungere una foto, aggiungere qualche parola chiave del profilo (molto utili per trovare nuovi contatti), e magari in quale nazione vivi, se non vuoi essere più specifico di così.\n\nNoi rispettiamo appieno la tua privacy, e nessuna di queste informazioni è necessaria o obbligatoria.\nSe sei nuovo e non conosci nessuno qui, possono aiutarti a trovare qualche nuovo e interessante contatto.\n\nGrazie e benvenuto su %2$s" -#: include/diaspora.php:719 +#: include/diaspora.php:720 msgid "Sharing notification from Diaspora network" msgstr "Notifica di condivisione dal network Diaspora*" -#: include/diaspora.php:2606 +#: include/diaspora.php:2625 msgid "Attachments:" msgstr "Allegati:" -#: include/items.php:4897 +#: include/delivery.php:533 +msgid "(no subject)" +msgstr "(nessun oggetto)" + +#: include/delivery.php:544 include/enotify.php:37 +msgid "noreply" +msgstr "nessuna risposta" + +#: include/items.php:4926 msgid "Do you really want to delete this item?" msgstr "Vuoi veramente cancellare questo elemento?" -#: include/items.php:5172 +#: include/items.php:5201 msgid "Archives" msgstr "Archivi" @@ -8302,11 +8322,11 @@ msgstr "Nome completo: %1$s\nIndirizzo del sito: %2$s\nNome utente: %3$s (%4$s)" msgid "Please visit %s to approve or reject the request." msgstr "Visita %s per approvare o rifiutare la richiesta." -#: include/oembed.php:220 +#: include/oembed.php:226 msgid "Embedded content" msgstr "Contenuto incorporato" -#: include/oembed.php:229 +#: include/oembed.php:235 msgid "Embedding disabled" msgstr "Embed disabilitato" @@ -8346,7 +8366,7 @@ msgstr[1] "%d contatti non importati" msgid "Done. You can now login with your username and password" msgstr "Fatto. Ora puoi entrare con il tuo nome utente e la tua password" -#: index.php:441 +#: index.php:442 msgid "toggle mobile" msgstr "commuta tema mobile" @@ -8364,7 +8384,6 @@ msgid "Set theme width" msgstr "Imposta la larghezza del tema" #: view/theme/cleanzero/config.php:86 view/theme/quattro/config.php:68 -#: view/theme/clean/config.php:88 msgid "Color scheme" msgstr "Schema colori" @@ -8486,56 +8505,6 @@ msgstr "Livello di zoom per Earth Layers" msgid "Show/hide boxes at right-hand column:" msgstr "Mostra/Nascondi riquadri nella colonna destra" -#: view/theme/clean/config.php:57 -msgid "Midnight" -msgstr "Mezzanotte" - -#: view/theme/clean/config.php:58 -msgid "Zenburn" -msgstr "Zenburn" - -#: view/theme/clean/config.php:59 -msgid "Bootstrap" -msgstr "Bootstrap" - -#: view/theme/clean/config.php:60 -msgid "Shades of Pink" -msgstr "Sfumature di Rosa" - -#: view/theme/clean/config.php:61 -msgid "Lime and Orange" -msgstr "Lime e Arancia" - -#: view/theme/clean/config.php:62 -msgid "GeoCities Retro" -msgstr "GeoCities Retro" - -#: view/theme/clean/config.php:86 -msgid "Background Image" -msgstr "Immagine di sfondo" - -#: view/theme/clean/config.php:86 -msgid "" -"The URL to a picture (e.g. from your photo album) that should be used as " -"background image." -msgstr "L'URL a un'immagine (p.e. dal tuo album foto) che viene usata come immagine di sfondo." - -#: view/theme/clean/config.php:87 -msgid "Background Color" -msgstr "Colore di sfondo" - -#: view/theme/clean/config.php:87 -msgid "HEX value for the background color. Don't include the #" -msgstr "Valore esadecimale del colore di sfondo. Non includere il #" - -#: view/theme/clean/config.php:89 -msgid "font size" -msgstr "dimensione font" - -#: view/theme/clean/config.php:89 -msgid "base font size for your interface" -msgstr "dimensione di base dei font per la tua interfaccia" - #: view/theme/vier/config.php:64 msgid "Comma separated list of helper forums" msgstr "Lista separata da virgola di forum di aiuto" diff --git a/view/it/strings.php b/view/it/strings.php index fb30c73573..606ae1e16c 100644 --- a/view/it/strings.php +++ b/view/it/strings.php @@ -8,8 +8,8 @@ function string_plural_select_it($n){ $a->strings["Network:"] = "Rete:"; $a->strings["Forum"] = "Forum"; $a->strings["%d contact edited."] = array( - 0 => "%d contatto modificato", - 1 => "%d contatti modificati", + 0 => "", + 1 => "", ); $a->strings["Could not access contact record."] = "Non è possibile accedere al contatto."; $a->strings["Could not locate selected profile."] = "Non riesco a trovare il profilo selezionato."; @@ -142,9 +142,6 @@ $a->strings["Edit your <strong>default</strong> profile to your liking. Review t $a->strings["Profile Keywords"] = "Parole chiave del profilo"; $a->strings["Set some public keywords for your default profile which describe your interests. We may be able to find other people with similar interests and suggest friendships."] = "Inserisci qualche parola chiave pubblica nel tuo profilo predefinito che descriva i tuoi interessi. Potremmo essere in grado di trovare altre persone con interessi similari e suggerirti delle amicizie."; $a->strings["Connecting"] = "Collegarsi"; -$a->strings["Facebook"] = "Facebook"; -$a->strings["Authorise the Facebook Connector if you currently have a Facebook account and we will (optionally) import all your Facebook friends and conversations."] = "Autorizza il Facebook Connector se hai un account Facebook, e noi (opzionalmente) importeremo tuti i tuoi amici e le tue conversazioni da Facebook."; -$a->strings["<em>If</em> this is your own personal server, installing the Facebook addon may ease your transition to the free social web."] = "<em>Se</em questo è il tuo server personale, installare il plugin per Facebook puo' aiutarti nella transizione verso il web sociale libero."; $a->strings["Importing Emails"] = "Importare le Email"; $a->strings["Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX"] = "Inserisci i tuoi dati di accesso all'email nella tua pagina Impostazioni Connettori se vuoi importare e interagire con amici o mailing list dalla tua casella di posta in arrivo"; $a->strings["Go to Your Contacts Page"] = "Vai alla tua pagina Contatti"; @@ -189,7 +186,7 @@ $a->strings["Tag removed"] = "Tag rimosso"; $a->strings["Remove Item Tag"] = "Rimuovi il tag"; $a->strings["Select a tag to remove: "] = "Seleziona un tag da rimuovere: "; $a->strings["Remove"] = "Rimuovi"; -$a->strings["Subsribing to OStatus contacts"] = "Iscrizione a contatti OStatus"; +$a->strings["Subscribing to OStatus contacts"] = ""; $a->strings["No contact provided."] = "Nessun contatto disponibile."; $a->strings["Couldn't fetch information for contact."] = "Non è stato possibile recuperare le informazioni del contatto."; $a->strings["Couldn't fetch friends for contact."] = "Non è stato possibile recuperare gli amici del contatto."; @@ -290,12 +287,6 @@ $a->strings["Forgot your Password?"] = "Hai dimenticato la password?"; $a->strings["Enter your email address and submit to have your password reset. Then check your email for further instructions."] = "Inserisci il tuo indirizzo email per reimpostare la password."; $a->strings["Nickname or Email: "] = "Nome utente o email: "; $a->strings["Reset"] = "Reimposta"; -$a->strings["event"] = "l'evento"; -$a->strings["%1\$s likes %2\$s's %3\$s"] = "A %1\$s piace %3\$s di %2\$s"; -$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "A %1\$s non piace %3\$s di %2\$s"; -$a->strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s parteciperà a %3\$s di %2\$s"; -$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s non parteciperà a %3\$s di %2\$s"; -$a->strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s forse parteciperà a %3\$s di %2\$s"; $a->strings["{0} wants to be your friend"] = "{0} vuole essere tuo amico"; $a->strings["{0} sent you a message"] = "{0} ti ha inviato un messaggio"; $a->strings["{0} requested registration"] = "{0} chiede la registrazione"; @@ -425,16 +416,22 @@ $a->strings["Site"] = "Sito"; $a->strings["Users"] = "Utenti"; $a->strings["Plugins"] = "Plugin"; $a->strings["Themes"] = "Temi"; +$a->strings["Additional features"] = "Funzionalità aggiuntive"; $a->strings["DB updates"] = "Aggiornamenti Database"; $a->strings["Inspect Queue"] = "Ispeziona Coda di invio"; +$a->strings["Federation Statistics"] = ""; $a->strings["Logs"] = "Log"; +$a->strings["View Logs"] = ""; $a->strings["probe address"] = "controlla indirizzo"; $a->strings["check webfinger"] = "verifica webfinger"; $a->strings["Admin"] = "Amministrazione"; $a->strings["Plugin Features"] = "Impostazioni Plugins"; $a->strings["diagnostics"] = "diagnostiche"; $a->strings["User registrations waiting for confirmation"] = "Utenti registrati in attesa di conferma"; +$a->strings["This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."] = ""; +$a->strings["The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here."] = ""; $a->strings["Administration"] = "Amministrazione"; +$a->strings["Currently this node is aware of %d nodes from the following platforms:"] = ""; $a->strings["ID"] = "ID"; $a->strings["Recipient Name"] = "Nome Destinatario"; $a->strings["Recipient Profile"] = "Profilo Destinatario"; @@ -585,6 +582,8 @@ $a->strings["Maximum Load Average (Frontend)"] = "Media Massimo Carico (Frontend $a->strings["Maximum system load before the frontend quits service - default 50."] = "Massimo carico di sistema prima che il frontend fermi il servizio - default 50."; $a->strings["Maximum table size for optimization"] = "Dimensione massima della tabella per l'ottimizzazione"; $a->strings["Maximum table size (in MB) for the automatic optimization - default 100 MB. Enter -1 to disable it."] = "La dimensione massima (in MB) per l'ottimizzazione automatica - default 100 MB. Inserisci -1 per disabilitarlo."; +$a->strings["Minimum level of fragmentation"] = ""; +$a->strings["Minimum fragmenation level to start the automatic optimization - default value is 30%."] = ""; $a->strings["Periodical check of global contacts"] = "Check periodico dei contatti globali"; $a->strings["If enabled, the global contacts are checked periodically for missing or outdated data and the vitality of the contacts and servers."] = "Se abilitato, i contatti globali sono controllati periodicamente per verificare dati mancanti o sorpassati e la vitaltà dei contatti e dei server."; $a->strings["Days between requery"] = "Giorni tra le richieste"; @@ -684,9 +683,11 @@ $a->strings["Toggle"] = "Inverti"; $a->strings["Author: "] = "Autore: "; $a->strings["Maintainer: "] = "Manutentore: "; $a->strings["Reload active plugins"] = "Ricarica i plugin attivi"; +$a->strings["There are currently no plugins available on your node. You can find the official plugin repository at %1\$s and might find other interesting plugins in the open plugin registry at %2\$s"] = ""; $a->strings["No themes found."] = "Nessun tema trovato."; $a->strings["Screenshot"] = "Anteprima"; $a->strings["Reload active themes"] = "Ricarica i temi attivi"; +$a->strings["No themes found on the system. They should be paced in %1\$s"] = ""; $a->strings["[Experimental]"] = "[Sperimentale]"; $a->strings["[Unsupported]"] = "[Non supportato]"; $a->strings["Log settings updated."] = "Impostazioni Log aggiornate."; @@ -695,11 +696,12 @@ $a->strings["Enable Debugging"] = "Abilita Debugging"; $a->strings["Log file"] = "File di Log"; $a->strings["Must be writable by web server. Relative to your Friendica top-level directory."] = "Deve essere scrivibile dal server web. Relativo alla tua directory Friendica."; $a->strings["Log level"] = "Livello di Log"; -$a->strings["Close"] = "Chiudi"; -$a->strings["FTP Host"] = "Indirizzo FTP"; -$a->strings["FTP Path"] = "Percorso FTP"; -$a->strings["FTP User"] = "Utente FTP"; -$a->strings["FTP Password"] = "Pasword FTP"; +$a->strings["PHP logging"] = ""; +$a->strings["To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."] = ""; +$a->strings["Off"] = "Spento"; +$a->strings["On"] = "Acceso"; +$a->strings["Lock feature %s"] = ""; +$a->strings["Manage Additional Features"] = ""; $a->strings["Search Results For: %s"] = "Risultato della ricerca per: %s"; $a->strings["Remove term"] = "Rimuovi termine"; $a->strings["Saved Searches"] = "Ricerche salvate"; @@ -921,7 +923,6 @@ $a->strings["Not available."] = "Non disponibile."; $a->strings["Community"] = "Comunità"; $a->strings["No results."] = "Nessun risultato."; $a->strings["everybody"] = "tutti"; -$a->strings["Additional features"] = "Funzionalità aggiuntive"; $a->strings["Display"] = "Visualizzazione"; $a->strings["Social Networks"] = "Social Networks"; $a->strings["Delegations"] = "Delegazioni"; @@ -958,8 +959,6 @@ $a->strings["No name"] = "Nessun nome"; $a->strings["Remove authorization"] = "Rimuovi l'autorizzazione"; $a->strings["No Plugin settings configured"] = "Nessun plugin ha impostazioni modificabili"; $a->strings["Plugin Settings"] = "Impostazioni plugin"; -$a->strings["Off"] = "Spento"; -$a->strings["On"] = "Acceso"; $a->strings["Additional Features"] = "Funzionalità aggiuntive"; $a->strings["General Social Media Settings"] = "Impostazioni Media Sociali"; $a->strings["Disable intelligent shortening"] = "Disabilita accorciamento intelligente"; @@ -1106,12 +1105,12 @@ $a->strings["Friends are advised to please try again in 24 hours."] = "Gli amici $a->strings["Invalid locator"] = "Invalid locator"; $a->strings["Invalid email address."] = "Indirizzo email non valido."; $a->strings["This account has not been configured for email. Request failed."] = "Questo account non è stato configurato per l'email. Richiesta fallita."; -$a->strings["Unable to resolve your name at the provided location."] = "Impossibile risolvere il tuo nome nella posizione indicata."; $a->strings["You have already introduced yourself here."] = "Ti sei già presentato qui."; $a->strings["Apparently you are already friends with %s."] = "Pare che tu e %s siate già amici."; $a->strings["Invalid profile URL."] = "Indirizzo profilo non valido."; $a->strings["Disallowed profile URL."] = "Indirizzo profilo non permesso."; $a->strings["Your introduction has been sent."] = "La tua presentazione è stata inviata."; +$a->strings["Remote subscription can't be done for your network. Please subscribe directly on your system."] = ""; $a->strings["Please login to confirm introduction."] = "Accedi per confermare la presentazione."; $a->strings["Incorrect identity currently logged in. Please login to <strong>this</strong> profile."] = "Non hai fatto accesso con l'identità corretta. Accedi a <strong>questo</strong> profilo."; $a->strings["Confirm"] = "Conferma"; @@ -1308,7 +1307,7 @@ $a->strings["poke, prod or do other things to somebody"] = "tocca, pungola o fai $a->strings["Recipient"] = "Destinatario"; $a->strings["Choose what you wish to do to recipient"] = "Scegli cosa vuoi fare al destinatario"; $a->strings["Make this post private"] = "Rendi questo post privato"; -$a->strings["Resubsribing to OStatus contacts"] = "Reiscrizione a contatti OStatus"; +$a->strings["Resubscribing to OStatus contacts"] = ""; $a->strings["Error"] = "Errore"; $a->strings["Total invitation limit exceeded."] = "Limite totale degli inviti superato."; $a->strings["%s : Not a valid email address."] = "%s: non è un indirizzo email valido."; @@ -1321,12 +1320,12 @@ $a->strings["%d message sent."] = array( ); $a->strings["You have no more invitations available"] = "Non hai altri inviti disponibili"; $a->strings["Visit %s for a list of public sites that you can join. Friendica members on other sites can all connect with each other, as well as with members of many other social networks."] = "Visita %s per una lista di siti pubblici a cui puoi iscriverti. I membri Friendica su altri siti possono collegarsi uno con l'altro, come con membri di molti altri social network."; -$a->strings["To accept this invitation, please visit and register at %s or any other public Friendica website."] = "Per accettare questo invito, visita e resitrati su %s o su un'altro sito web Friendica aperto al pubblico."; +$a->strings["To accept this invitation, please visit and register at %s or any other public Friendica website."] = "Per accettare questo invito, visita e registrati su %s o su un'altro sito web Friendica aperto al pubblico."; $a->strings["Friendica sites all inter-connect to create a huge privacy-enhanced social web that is owned and controlled by its members. They can also connect with many traditional social networks. See %s for a list of alternate Friendica sites you can join."] = "I siti Friendica son tutti collegati tra loro per creare una grossa rete sociale rispettosa della privacy, posseduta e controllata dai suoi membri. I siti Friendica possono anche collegarsi a molti altri social network tradizionali. Vai su %s per una lista di siti Friendica alternativi a cui puoi iscriverti."; $a->strings["Our apologies. This system is not currently configured to connect with other public sites or invite members."] = "Ci scusiamo, questo sistema non è configurato per collegarsi con altri siti pubblici o per invitare membri."; $a->strings["Send invitations"] = "Invia inviti"; $a->strings["Enter email addresses, one per line:"] = "Inserisci gli indirizzi email, uno per riga:"; -$a->strings["You are cordially invited to join me and other close friends on Friendica - and help us to create a better social web."] = "Sei cordialmente invitato a unirti a me ed ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore."; +$a->strings["You are cordially invited to join me and other close friends on Friendica - and help us to create a better social web."] = "Sei cordialmente invitato/a ad unirti a me e ad altri amici su Friendica, e ad aiutarci a creare una rete sociale migliore."; $a->strings["You will need to supply this invitation code: \$invite_code"] = "Sarà necessario fornire questo codice invito: \$invite_code"; $a->strings["Once you have registered, please connect with me via my profile page at:"] = "Una volta registrato, connettiti con me dal mio profilo:"; $a->strings["For more information about the Friendica project and why we feel it is important, please visit http://friendica.com"] = "Per maggiori informazioni sul progetto Friendica e perchè pensiamo sia importante, visita http://friendica.com"; @@ -1563,11 +1562,18 @@ $a->strings["Forums:"] = "Forum:"; $a->strings["Videos"] = "Video"; $a->strings["Events and Calendar"] = "Eventi e calendario"; $a->strings["Only You Can See This"] = "Solo tu puoi vedere questo"; +$a->strings["event"] = "l'evento"; +$a->strings["%1\$s likes %2\$s's %3\$s"] = "A %1\$s piace %3\$s di %2\$s"; +$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "A %1\$s non piace %3\$s di %2\$s"; +$a->strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s parteciperà a %3\$s di %2\$s"; +$a->strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s non parteciperà a %3\$s di %2\$s"; +$a->strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s forse parteciperà a %3\$s di %2\$s"; $a->strings["Post to Email"] = "Invia a email"; $a->strings["Connectors disabled, since \"%s\" is enabled."] = "Connettore disabilitato, dato che \"%s\" è abilitato."; $a->strings["Visible to everybody"] = "Visibile a tutti"; $a->strings["show"] = "mostra"; $a->strings["don't show"] = "non mostrare"; +$a->strings["Close"] = "Chiudi"; $a->strings["[no subject]"] = "[nessun oggetto]"; $a->strings["stopped following"] = "tolto dai seguiti"; $a->strings["View Status"] = "Visualizza stato"; @@ -1697,8 +1703,6 @@ $a->strings["<a href=\"%1\$s\" target=\"_blank\">%2\$s</a> %3\$s"] = "<a href=\" $a->strings["<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a href=\"%s\" target=\"_blank\">post</a>"] = "<span><a href=\"%s\" target=\"_blank\">%s</a> ha scritto il seguente <a href=\"%s\" target=\"_blank\">messaggio</a>"; $a->strings["$1 wrote:"] = "$1 ha scritto:"; $a->strings["Encrypted content"] = "Contenuto criptato"; -$a->strings["(no subject)"] = "(nessun oggetto)"; -$a->strings["noreply"] = "nessuna risposta"; $a->strings["Cannot locate DNS info for database server '%s'"] = "Non trovo le informazioni DNS per il database server '%s'"; $a->strings["Unknown | Not categorised"] = "Sconosciuto | non categorizzato"; $a->strings["Block immediately"] = "Blocca immediatamente"; @@ -1710,6 +1714,7 @@ $a->strings["Weekly"] = "Settimanalmente"; $a->strings["Monthly"] = "Mensilmente"; $a->strings["OStatus"] = "Ostatus"; $a->strings["RSS/Atom"] = "RSS / Atom"; +$a->strings["Facebook"] = "Facebook"; $a->strings["Zot!"] = "Zot!"; $a->strings["LinkedIn"] = "LinkedIn"; $a->strings["XMPP/IM"] = "XMPP/IM"; @@ -1765,15 +1770,9 @@ $a->strings["Manage/edit friends and contacts"] = "Gestisci/modifica amici e con $a->strings["Site setup and configuration"] = "Configurazione del sito"; $a->strings["Navigation"] = "Navigazione"; $a->strings["Site map"] = "Mappa del sito"; -$a->strings["User not found."] = "Utente non trovato."; $a->strings["Daily posting limit of %d posts reached. The post was rejected."] = "Limite giornaliero di %d messaggi raggiunto. Il messaggio è stato rifiutato"; $a->strings["Weekly posting limit of %d posts reached. The post was rejected."] = "Limite settimanale di %d messaggi raggiunto. Il messaggio è stato rifiutato"; $a->strings["Monthly posting limit of %d posts reached. The post was rejected."] = "Limite mensile di %d messaggi raggiunto. Il messaggio è stato rifiutato"; -$a->strings["There is no status with this id."] = "Non c'è nessuno status con questo id."; -$a->strings["There is no conversation with this id."] = "Non c'è nessuna conversazione con questo id"; -$a->strings["Invalid item."] = "Elemento non valido."; -$a->strings["Invalid action. "] = "Azione non valida."; -$a->strings["DB error"] = "Errore database"; $a->strings["An invitation is required."] = "E' richiesto un invito."; $a->strings["Invitation could not be verified."] = "L'invito non puo' essere verificato."; $a->strings["Invalid OpenID url"] = "Url OpenID non valido"; @@ -1796,6 +1795,8 @@ $a->strings["\n\t\tDear %1\$s,\n\t\t\tThank you for registering at %2\$s. Your a $a->strings["\n\t\tThe login details are as follows:\n\t\t\tSite Location:\t%3\$s\n\t\t\tLogin Name:\t%1\$s\n\t\t\tPassword:\t%5\$s\n\n\t\tYou may change your password from your account \"Settings\" page after logging\n\t\tin.\n\n\t\tPlease take a few moments to review the other account settings on that page.\n\n\t\tYou may also wish to add some basic information to your default profile\n\t\t(on the \"Profiles\" page) so that other people can easily find you.\n\n\t\tWe recommend setting your full name, adding a profile photo,\n\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n\t\tperhaps what country you live in; if you do not wish to be more specific\n\t\tthan that.\n\n\t\tWe fully respect your right to privacy, and none of these items are necessary.\n\t\tIf you are new and do not know anybody here, they may help\n\t\tyou to make some new and interesting friends.\n\n\n\t\tThank you and welcome to %2\$s."] = "\nI dettagli del tuo utente sono:\n Indirizzo del sito: %3\$s\n Nome utente: %1\$s\n Password: %5\$s\n\nPuoi cambiare la tua password dalla pagina delle impostazioni del tuo account dopo esserti autenticato.\n\nPer favore, prenditi qualche momento per esaminare tutte le impostazioni presenti.\n\nPotresti voler aggiungere qualche informazione di base al tuo profilo predefinito (nella pagina \"Profili\"), così che le altre persone possano trovarti più facilmente.\n\nTi raccomandiamo di inserire il tuo nome completo, aggiungere una foto, aggiungere qualche parola chiave del profilo (molto utili per trovare nuovi contatti), e magari in quale nazione vivi, se non vuoi essere più specifico di così.\n\nNoi rispettiamo appieno la tua privacy, e nessuna di queste informazioni è necessaria o obbligatoria.\nSe sei nuovo e non conosci nessuno qui, possono aiutarti a trovare qualche nuovo e interessante contatto.\n\nGrazie e benvenuto su %2\$s"; $a->strings["Sharing notification from Diaspora network"] = "Notifica di condivisione dal network Diaspora*"; $a->strings["Attachments:"] = "Allegati:"; +$a->strings["(no subject)"] = "(nessun oggetto)"; +$a->strings["noreply"] = "nessuna risposta"; $a->strings["Do you really want to delete this item?"] = "Vuoi veramente cancellare questo elemento?"; $a->strings["Archives"] = "Archivi"; $a->strings["Male"] = "Maschio"; @@ -1956,18 +1957,6 @@ $a->strings["Your personal photos"] = "Le tue foto personali"; $a->strings["Local Directory"] = "Elenco Locale"; $a->strings["Set zoomfactor for Earth Layers"] = "Livello di zoom per Earth Layers"; $a->strings["Show/hide boxes at right-hand column:"] = "Mostra/Nascondi riquadri nella colonna destra"; -$a->strings["Midnight"] = "Mezzanotte"; -$a->strings["Zenburn"] = "Zenburn"; -$a->strings["Bootstrap"] = "Bootstrap"; -$a->strings["Shades of Pink"] = "Sfumature di Rosa"; -$a->strings["Lime and Orange"] = "Lime e Arancia"; -$a->strings["GeoCities Retro"] = "GeoCities Retro"; -$a->strings["Background Image"] = "Immagine di sfondo"; -$a->strings["The URL to a picture (e.g. from your photo album) that should be used as background image."] = "L'URL a un'immagine (p.e. dal tuo album foto) che viene usata come immagine di sfondo."; -$a->strings["Background Color"] = "Colore di sfondo"; -$a->strings["HEX value for the background color. Don't include the #"] = "Valore esadecimale del colore di sfondo. Non includere il #"; -$a->strings["font size"] = "dimensione font"; -$a->strings["base font size for your interface"] = "dimensione di base dei font per la tua interfaccia"; $a->strings["Comma separated list of helper forums"] = "Lista separata da virgola di forum di aiuto"; $a->strings["Set style"] = "Imposta stile"; $a->strings["Quick Start"] = "Quick Start"; From 8a9862c9cedcf0f05aea3ad892d4a7cb774beb1b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 1 Feb 2016 00:09:03 +0100 Subject: [PATCH 012/273] Entry import could work but need clean up --- include/import-dfrn.php | 684 +++++++++++++++++++++++----------------- 1 file changed, 396 insertions(+), 288 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 8a72e40060..0e185d3f44 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -18,7 +18,12 @@ require_once("include/items.php"); require_once("include/tags.php"); require_once("include/files.php"); +define('DFRN_TOP_LEVEL', 0); +define('DFRN_REPLY', 1); +define('DFRN_REPLY_RC', 2); + class dfrn2 { + /** * @brief Add new birthday event for this person * @@ -31,11 +36,11 @@ class dfrn2 { logger('updating birthday: '.$birthday.' for contact '.$contact['id']); $bdtext = sprintf(t('%s\'s birthday'), $contact['name']); - $bdtext2 = sprintf(t('Happy Birthday %s'), ' [url=' . $contact['url'].']'.$contact['name'].'[/url]' ) ; + $bdtext2 = sprintf(t('Happy Birthday %s'), ' [url=' . $contact['url'].']'.$contact['name'].'[/url]') ; $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", intval($contact['uid']), intval($contact['id']), dbesc(datetime_convert()), @@ -310,7 +315,7 @@ class dfrn2 { $suggest["name"] = $xpath->query('dfrn:name/text()', $suggestion)->item(0)->nodeValue; $suggest["photo"] = $xpath->query('dfrn:photo/text()', $suggestion)->item(0)->nodeValue; $suggest["request"] = $xpath->query('dfrn:request/text()', $suggestion)->item(0)->nodeValue; - $suggest["note"] = $xpath->query('dfrn:note/text()', $suggestion)->item(0)->nodeValue; + $suggest["body"] = $xpath->query('dfrn:note/text()', $suggestion)->item(0)->nodeValue; // Does our member already have a friend matching this description? @@ -474,12 +479,115 @@ class dfrn2 { return true; } + private function upate_content($current, $item, $importer, $entrytype) { + if (edited_timestamp_is_newer($current, $item)) { + + // do not accept (ignore) an earlier edit than one we currently have. + if(datetime_convert('UTC','UTC',$item['edited']) < $current['edited']) + return; + + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + dbesc($item['title']), + dbesc($item['body']), + dbesc($item['tag']), + dbesc(datetime_convert('UTC','UTC',$item['edited'])), + dbesc(datetime_convert()), + dbesc($item["uri"]), + intval($importer['importer_uid']) + ); + create_tags_from_itemuri($item["uri"], $importer['importer_uid']); + update_thread_uri($item["uri"], $importer['importer_uid']); + + if ($entrytype == DFRN_REPLY_RC) + proc_run('php',"include/notifier.php","comment-import",$current["id"]); + } + + // update last-child if it changes + if($item["last-child"] AND ($item["last-child"] != $current['last-child'])) { + $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc(datetime_convert()), + dbesc($item["parent-uri"]), + intval($importer['importer_uid']) + ); + $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + intval($item["last-child"]), + dbesc(datetime_convert()), + dbesc($item["uri"]), + intval($importer['importer_uid']) + ); + } + } + + private function get_entry_type($is_reply, $importer, $item) { + if ($is_reply) { + $community = false; + + if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP) { + $sql_extra = ''; + $community = true; + logger('possible community reply'); + } else + $sql_extra = " and contact.self = 1 and item.wall = 1 "; + + // was the top-level post for this reply written by somebody on this site? + // Specifically, the recipient? + + $is_a_remote_comment = false; + + $r = q("SELECT `item`.`parent-uri` FROM `item` + WHERE `item`.`uri` = '%s' + LIMIT 1", + dbesc($item["parent-uri"]) + ); + if($r && count($r)) { + $r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item` + INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' OR `item`.`thr-parent` = '%s') + AND `item`.`uid` = %d + $sql_extra + LIMIT 1", + dbesc($r[0]['parent-uri']), + dbesc($r[0]['parent-uri']), + dbesc($r[0]['parent-uri']), + intval($importer['importer_uid']) + ); + if($r && count($r)) + $is_a_remote_comment = true; + } + + // Does this have the characteristics of a community or private group comment? + // If it's a reply to a wall post on a community/prvgroup page it's a + // valid community comment. Also forum_mode makes it valid for sure. + // If neither, it's not. + + if($is_a_remote_comment && $community) { + if((!$r[0]['forum_mode']) && (!$r[0]['wall'])) { + $is_a_remote_comment = false; + logger('not a community reply'); + } + } + } else { + $is_reply = false; + $is_a_remote_comment = false; + } + + if ($is_a_remote_comment) + return DFRN_REPLY_RC; + elseif ($is_reply) + return DFRN_REPLY; + else + return DFRN_TOP_LEVEL; + } + private function process_entry($header, $xpath, $entry, $importer, $contact) { logger("Processing entries"); $item = $header; + // Get the uri + $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; + // Fetch the owner $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", $contact, true); @@ -506,32 +614,6 @@ class dfrn2 { if (($header["network"] != $author["network"]) AND ($author["network"] != "")) $item["network"] = $author["network"]; - // Now get the item - $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["uri"])); - //if ($r) { - // logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); - // return false; - //} - - // Is it a reply? - $inreplyto = $xpath->query('thr:in-reply-to', $entry); - if (is_object($inreplyto->item(0))) { - $objecttype = ACTIVITY_OBJ_COMMENT; - $item["type"] = 'remote-comment'; - $item["gravity"] = GRAVITY_COMMENT; - - foreach($inreplyto->item(0)->attributes AS $attributes) { - if ($attributes->name == "ref") - $item["parent-uri"] = $attributes->textContent; - } - } else { - $objecttype = ACTIVITY_OBJ_NOTE; - $item["parent-uri"] = $item["uri"]; - } - $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; @@ -574,14 +656,12 @@ class dfrn2 { $item["guid"] = $xpath->query('dfrn:diaspora_guid/text()', $entry)->item(0)->nodeValue; // We store the data from "dfrn:diaspora_signature" in a later step. See some lines below - $signature = $xpath->query('dfrn:diaspora_signature/text()', $entry)->item(0)->nodeValue; + $item["dsprsig"] = unxmlify($xpath->query('dfrn:diaspora_signature/text()', $entry)->item(0)->nodeValue); $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; if ($xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue != "") - $objecttype = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; - - $item["object-type"] = $objecttype; + $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; // I have the feeling that we don't do anything with this data $object = $xpath->query('activity:object', $entry)->item(0); @@ -643,262 +723,231 @@ class dfrn2 { } } -/* -// reply - // not allowed to post + // Is it a reply or a top level posting? + $item["parent-uri"] = $item["uri"]; - if($contact['rel'] == CONTACT_IS_FOLLOWER) - continue; + $inreplyto = $xpath->query('thr:in-reply-to', $entry); + if (is_object($inreplyto->item(0))) + foreach($inreplyto->item(0)->attributes AS $attributes) + if ($attributes->name == "ref") + $item["parent-uri"] = $attributes->textContent; - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); + $entrytype = get_entry_type(( $item["parent-uri"] != $item["uri"]), $importer, $item); - // Update content if 'updated' changes + // Now assign the rest of the values that depend on the type of the message + if ($entrytype == DFRN_REPLY_RC) { + if (!isset($item["object-type"])) + $item["object-type"] = ACTIVITY_OBJ_COMMENT; - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = - '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - create_tags_from_itemuri($item_id, $importer['uid']); - update_thread_uri($item_id, $importer['uid']); - } - - // update last-child if it changes - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) { - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($parent_uri), - intval($importer['uid']) - ); - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - update_thread_uri($item_id, $importer['uid']); - } - continue; - } - if(($datarray['verb'] === ACTIVITY_LIKE) - || ($datarray['verb'] === ACTIVITY_DISLIKE) - || ($datarray['verb'] === ACTIVITY_ATTEND) - || ($datarray['verb'] === ACTIVITY_ATTENDNO) - || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - } - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($r)) - continue; - - // extract tag, if not duplicate, add to parent item - if($xo->id && $xo->content) { - $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; - if(! (stristr($r[0]['tag'],$newtag))) { - q("UPDATE item SET tag = '%s' WHERE id = %d", - dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), - intval($r[0]['id']) - ); - create_tags_from_item($r[0]['id']); - } - } - } - } - - - -// toplevel - // special handling for events - - if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { - $ev = bbtoevent($datarray['body']); - if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item_id; - $ev['edited'] = $datarray['edited']; - $ev['private'] = $datarray['private']; - $ev['guid'] = $datarray['guid']; - - if(is_array($contact)) - $ev['cid'] = $contact['id']; - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - if(count($r)) - $ev['id'] = $r[0]['id']; - $xyz = event_store($ev); - continue; - } - } - - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = - '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - create_tags_from_itemuri($item_id, $importer['uid']); - update_thread_uri($item_id, $importer['uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if($allow && $allow[0]['data'] != $r[0]['last-child']) { - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - update_thread_uri($item_id, $importer['uid']); - } - continue; - } - - - -toplevel: - - if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) { - logger('consume-feed: New follower'); - new_follower($importer,$contact,$datarray,$item); - return; - } - if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) { - lose_follower($importer,$contact,$datarray,$item); - return; - } - - if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) { - logger('consume-feed: New friend request'); - new_follower($importer,$contact,$datarray,$item,true); - return; - } - if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) { - lose_sharer($importer,$contact,$datarray,$item); - return; - } - - - if(! is_array($contact)) - return; - - if(! link_compare($datarray['owner-link'],$contact['url'])) { - // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - // but otherwise there's a possible data mixup on the sender's system. - // the tgroup delivery code called from item_store will correct it if it's a forum, - // but we're going to unconditionally correct it here so that the post will always be owned by our contact. - logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - $datarray['owner-avatar'] = $contact['thumb']; - } - - // We've allowed "followers" to reach this point so we can decide if they are - // posting an @-tag delivery, which followers are allowed to do for certain - // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it. - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray))) - continue; - - // This is my contact on another system, but it's really me. - // Turn this into a wall post. - $notify = item_is_remote_self($contact, $datarray); - -*/ - print_r($item); - return; - //$item_id = item_store($item); - - if (!$item_id) { - logger("Error storing item", LOGGER_DEBUG); - return false; + $item["type"] = 'remote-comment'; + $item['wall'] = 1; + } elseif ($entrytype == DFRN_REPLY) { + if (!isset($item["object-type"])) + $item["object-type"] = ACTIVITY_OBJ_COMMENT; } else { - logger("Item was stored with id ".$item_id, LOGGER_DEBUG); + if (!isset($item["object-type"])) + $item["object-type"] = ACTIVITY_OBJ_NOTE; - if ($signature) { - $signature = json_decode(base64_decode($signature)); + if ($item["object-type"] === ACTIVITY_OBJ_EVENT) { + $ev = bbtoevent($item['body']); + if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { + $ev['cid'] = $importer['id']; + $ev['uid'] = $importer['uid']; + $ev['uri'] = $item["uri"]; + $ev['edited'] = $item['edited']; + $ev['private'] = $item['private']; + $ev['guid'] = $item['guid']; - // Check for falsely double encoded signatures - $signature->signature = diaspora_repair_signature($signature->signature, $signature->signer); - - // Store it in the "sign" table where we will read it for comments that we relay to Diaspora - q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')", - intval($item_id), - dbesc($signature->signed_text), - dbesc($signature->signature), - dbesc($signature->signer) - ); + $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item["uri"]), + intval($importer['uid']) + ); + if(count($r)) + $ev['id'] = $r[0]['id']; + $xyz = event_store($ev); + return; + } + } + } + + $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item["uri"]), + intval($importer['importer_uid']) + ); + + // Update content if 'updated' changes + if(count($r)) { + self::upate_content($r[0], $item, $importer, $entrytype); + return; + } + + if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { + if($importer['rel'] == CONTACT_IS_FOLLOWER) + return; + + if(($item['verb'] === ACTIVITY_LIKE) + || ($item['verb'] === ACTIVITY_DISLIKE) + || ($item['verb'] === ACTIVITY_ATTEND) + || ($item['verb'] === ACTIVITY_ATTENDNO) + || ($item['verb'] === ACTIVITY_ATTENDMAYBE)) { + $is_like = true; + $item['type'] = 'activity'; + $item['gravity'] = GRAVITY_LIKE; + // only one like or dislike per person + // splitted into two queries for performance issues + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", + intval($item['uid']), + dbesc($item['author-link']), + dbesc($item['verb']), + dbesc($item['parent-uri']) + ); + if($r && count($r)) + return; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", + intval($item['uid']), + dbesc($item['author-link']), + dbesc($item['verb']), + dbesc($item['parent-uri']) + ); + if($r && count($r)) + return; + + } else + $is_like = false; + + if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) { + + $xo = parse_xml_string($item['object'],false); + $xt = parse_xml_string($item['target'],false); + + if($xt->type == ACTIVITY_OBJ_NOTE) { + $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($xt->id), + intval($importer['importer_uid']) + ); + + if(!count($r)) + return; + + // extract tag, if not duplicate, add to parent item + if($xo->content) { + if(!(stristr($r[0]['tag'],trim($xo->content)))) { + q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", + dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), + intval($r[0]['id']) + ); + create_tags_from_item($r[0]['id']); + } + } + } + } + + $posted_id = item_store($item); + $parent = 0; + + if($posted_id) { + + $item["id"] = $posted_id; + + $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($posted_id), + intval($importer['importer_uid']) + ); + if(count($r)) { + $parent = $r[0]['parent']; + $parent_uri = $r[0]['parent-uri']; + } + + if(!$is_like) { + $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", + dbesc(datetime_convert()), + intval($importer['importer_uid']), + intval($r[0]['parent']) + ); + + $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", + dbesc(datetime_convert()), + intval($importer['importer_uid']), + intval($posted_id) + ); + } + + if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) { + proc_run('php',"include/notifier.php","comment-import","$posted_id"); + } + + return true; + } + } else { + if(!link_compare($item['owner-link'],$importer['url'])) { + // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, + // but otherwise there's a possible data mixup on the sender's system. + // the tgroup delivery code called from item_store will correct it if it's a forum, + // but we're going to unconditionally correct it here so that the post will always be owned by our contact. + logger('Correcting item owner.', LOGGER_DEBUG); + $item['owner-name'] = $importer['senderName']; + $item['owner-link'] = $importer['url']; + $item['owner-avatar'] = $importer['thumb']; + } + + if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer['importer_uid'],$item))) + return; + + // This is my contact on another system, but it's really me. + // Turn this into a wall post. + $notify = item_is_remote_self($importer, $item); + + $posted_id = item_store($item, false, $notify); + + if(stristr($item['verb'],ACTIVITY_POKE)) { + $verb = urldecode(substr($item['verb'],strpos($item['verb'],'#')+1)); + if(!$verb) + return; + $xo = parse_xml_string($item['object'],false); + + if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { + + // somebody was poked/prodded. Was it me? + $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); + + foreach($links->link as $l) { + $atts = $l->attributes(); + switch($atts['rel']) { + case "alternate": + $Blink = $atts['href']; + break; + default: + break; + } + } + if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) { + + // send a notification + require_once('include/enotify.php'); + + notification(array( + 'type' => NOTIFY_POKE, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' => $importer['importer_uid'], + 'item' => $item, + 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)), + 'source_name' => stripslashes($item['author-name']), + 'source_link' => $item['author-link'], + 'source_photo' => ((link_compare($item['author-link'],$importer['url'])) + ? $importer['thumb'] : $item['author-avatar']), + 'verb' => $item['verb'], + 'otype' => 'person', + 'activity' => $verb, + 'parent' => $item['parent'] + )); + } + } } } - return $item_id; } private function process_deletion($header, $xpath, $deletion, $importer, $contact_id) { @@ -919,6 +968,64 @@ toplevel: if (!$uri OR !$contact_id) return false; + + $is_reply = false; + $r = q("SELECT `id`, `parent-uri`, `parent` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($uri), + intval($importer['importer_uid']) + ); + if(count($r)) { + $parent_uri = $r[0]['parent-uri']; + if($r[0]['id'] != $r[0]['parent']) + $is_reply = true; + } + + if($is_reply) { + $community = false; + + if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP) { + $sql_extra = ''; + $community = true; + logger('possible community delete'); + } else + $sql_extra = " AND `contact`.`self` AND `item`.`wall`"; + + // was the top-level post for this reply written by somebody on this site? + // Specifically, the recipient? + + $is_a_remote_delete = false; + + $r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item` + INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') + AND `item`.`uid` = %d + $sql_extra + LIMIT 1", + dbesc($parent_uri), + dbesc($parent_uri), + dbesc($parent_uri), + intval($importer['importer_uid']) + ); + if($r && count($r)) + $is_a_remote_delete = true; + + // Does this have the characteristics of a community or private group comment? + // If it's a reply to a wall post on a community/prvgroup page it's a + // valid community comment. Also forum_mode makes it valid for sure. + // If neither, it's not. + + if($is_a_remote_delete && $community) { + if((!$r[0]['forum_mode']) && (!$r[0]['wall'])) { + $is_a_remote_delete = false; + logger('not a community delete'); + } + } + + if($is_a_remote_delete) { + logger('received remote delete'); + } + } + $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", dbesc($uri), @@ -932,6 +1039,8 @@ toplevel: if(!$item["deleted"]) logger('deleting item '.$item["id"].' uri='.$item['uri'], LOGGER_DEBUG); + else + return; if($item["object-type"] === ACTIVITY_OBJ_EVENT) { logger("Deleting event ".$item["event-id"], LOGGER_DEBUG); @@ -955,7 +1064,7 @@ toplevel: $author_copy = (($item['origin']) ? true : false); if($owner_remove && $author_copy) - continue; + return; if($author_remove || $owner_remove) { $tags = explode(',',$i[0]['tag']); $newtags = array(); @@ -983,9 +1092,9 @@ toplevel: dbesc($item['uri']), intval($importer['uid']) ); - create_tags_from_itemuri($item['uri'], $importer['uid']); - create_files_from_itemuri($item['uri'], $importer['uid']); - update_thread_uri($item['uri'], $importer['uid']); + create_tags_from_itemuri($item['uri'], $importer['uid']); + create_files_from_itemuri($item['uri'], $importer['uid']); + update_thread_uri($item['uri'], $importer['uid']); } else { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '', `title` = '' @@ -997,6 +1106,7 @@ toplevel: ); create_tags_from_itemuri($uri, $importer['uid']); create_files_from_itemuri($uri, $importer['uid']); + update_thread_uri($uri, $importer['importer_uid']); if($item['last-child']) { // ensure that last-child is set in case the comment that had it just got wiped. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", @@ -1018,9 +1128,8 @@ toplevel: } // if this is a relayed delete, propagate it to other recipients -// if($is_a_remote_delete) - // proc_run('php',"include/notifier.php","drop",$item['id']); - + if($is_a_remote_delete) + proc_run('php',"include/notifier.php","drop",$item['id']); } } } @@ -1056,7 +1165,6 @@ toplevel: $header["type"] = "remote"; $header["wall"] = 0; $header["origin"] = 0; - $header["gravity"] = GRAVITY_PARENT; $header["contact-id"] = $importer["id"]; // Update the contact table if the data has changed From 1aa225b03bc89e53cbaa5ec95e4c13b87587a926 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 1 Feb 2016 23:52:37 +0100 Subject: [PATCH 013/273] Code beautification --- include/import-dfrn.php | 642 ++++++++++++++++++---------------------- 1 file changed, 294 insertions(+), 348 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 0e185d3f44..bfcb002bb1 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -18,9 +18,9 @@ require_once("include/items.php"); require_once("include/tags.php"); require_once("include/files.php"); -define('DFRN_TOP_LEVEL', 0); -define('DFRN_REPLY', 1); -define('DFRN_REPLY_RC', 2); +define("DFRN_TOP_LEVEL", 0); +define("DFRN_REPLY", 1); +define("DFRN_REPLY_RC", 2); class dfrn2 { @@ -33,23 +33,23 @@ class dfrn2 { */ private function birthday_event($contact, $birthday) { - logger('updating birthday: '.$birthday.' for contact '.$contact['id']); + logger("updating birthday: ".$birthday." for contact ".$contact["id"]); - $bdtext = sprintf(t('%s\'s birthday'), $contact['name']); - $bdtext2 = sprintf(t('Happy Birthday %s'), ' [url=' . $contact['url'].']'.$contact['name'].'[/url]') ; + $bdtext = sprintf(t("%s\'s birthday"), $contact["name"]); + $bdtext2 = sprintf(t("Happy Birthday %s"), " [url=".$contact["url"]."]".$contact["name"]."[/url]") ; $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", - intval($contact['uid']), - intval($contact['id']), + intval($contact["uid"]), + intval($contact["id"]), dbesc(datetime_convert()), dbesc(datetime_convert()), - dbesc(datetime_convert('UTC','UTC', $birthday)), - dbesc(datetime_convert('UTC','UTC', $birthday.' + 1 day ')), + dbesc(datetime_convert("UTC","UTC", $birthday)), + dbesc(datetime_convert("UTC","UTC", $birthday." + 1 day ")), dbesc($bdtext), dbesc($bdtext2), - dbesc('birthday') + dbesc("birthday") ); } @@ -68,8 +68,8 @@ class dfrn2 { private function fetchauthor($xpath, $context, $importer, $element, $contact, $onlyfetch) { $author = array(); - $author["name"] = $xpath->evaluate($element.'/atom:name/text()', $context)->item(0)->nodeValue; - $author["link"] = $xpath->evaluate($element.'/atom:uri/text()', $context)->item(0)->nodeValue; + $author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue; + $author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue; $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd` @@ -124,23 +124,23 @@ class dfrn2 { $contact["uri-date"] = $attributes->textContent; // Update contact data - $value = $xpath->evaluate($element.'/dfrn:handle/text()', $context)->item(0)->nodeValue; + $value = $xpath->evaluate($element."/dfrn:handle/text()", $context)->item(0)->nodeValue; if ($value != "") $contact["addr"] = $value; - $value = $xpath->evaluate($element.'/poco:displayName/text()', $context)->item(0)->nodeValue; + $value = $xpath->evaluate($element."/poco:displayName/text()", $context)->item(0)->nodeValue; if ($value != "") $contact["name"] = $value; - $value = $xpath->evaluate($element.'/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + $value = $xpath->evaluate($element."/poco:preferredUsername/text()", $context)->item(0)->nodeValue; if ($value != "") $contact["nick"] = $value; - $value = $xpath->evaluate($element.'/poco:note/text()', $context)->item(0)->nodeValue; + $value = $xpath->evaluate($element."/poco:note/text()", $context)->item(0)->nodeValue; if ($value != "") $contact["about"] = $value; - $value = $xpath->evaluate($element.'/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; + $value = $xpath->evaluate($element."/poco:address/poco:formatted/text()", $context)->item(0)->nodeValue; if ($value != "") $contact["location"] = $value; @@ -154,7 +154,7 @@ class dfrn2 { // Save the keywords into the contact table $tags = array(); - $tagelements = $xpath->evaluate($element.'/poco:tags/text()', $context); + $tagelements = $xpath->evaluate($element."/poco:tags/text()", $context); foreach($tagelements AS $tag) $tags[$tag->nodeValue] = $tag->nodeValue; @@ -164,7 +164,7 @@ class dfrn2 { // "dfrn:birthday" contains the birthday converted to UTC $old_bdyear = $contact["bdyear"]; - $birthday = $xpath->evaluate($element.'/dfrn:birthday/text()', $context)->item(0)->nodeValue; + $birthday = $xpath->evaluate($element."/dfrn:birthday/text()", $context)->item(0)->nodeValue; if (strtotime($birthday) > time()) { $bd_timestamp = strtotime($birthday); @@ -173,7 +173,7 @@ class dfrn2 { } // "poco:birthday" is the birthday in the format "yyyy-mm-dd" - $value = $xpath->evaluate($element.'/poco:birthday/text()', $context)->item(0)->nodeValue; + $value = $xpath->evaluate($element."/poco:birthday/text()", $context)->item(0)->nodeValue; if (!in_array($value, array("", "0000-00-00"))) { $bdyear = date("Y"); @@ -229,27 +229,27 @@ class dfrn2 { if (!is_object($activity)) return ""; - $obj_doc = new DOMDocument('1.0', 'utf-8'); + $obj_doc = new DOMDocument("1.0", "utf-8"); $obj_doc->formatOutput = true; $obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element); - $activity_type = $xpath->query('activity:object-type/text()', $activity)->item(0)->nodeValue; + $activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue; xml_add_element($obj_doc, $obj_element, "type", $activity_type); - $id = $xpath->query('atom:id', $activity)->item(0); + $id = $xpath->query("atom:id", $activity)->item(0); if (is_object($id)) $obj_element->appendChild($obj_doc->importNode($id, true)); - $title = $xpath->query('atom:title', $activity)->item(0); + $title = $xpath->query("atom:title", $activity)->item(0); if (is_object($title)) $obj_element->appendChild($obj_doc->importNode($title, true)); - $link = $xpath->query('atom:link', $activity)->item(0); + $link = $xpath->query("atom:link", $activity)->item(0); if (is_object($link)) $obj_element->appendChild($obj_doc->importNode($link, true)); - $content = $xpath->query('atom:content', $activity)->item(0); + $content = $xpath->query("atom:content", $activity)->item(0); if (is_object($content)) $obj_element->appendChild($obj_doc->importNode($content, true)); @@ -258,7 +258,7 @@ class dfrn2 { $objxml = $obj_doc->saveXML($obj_element); // @todo This isn't totally clean. We should find a way to transform the namespaces - $objxml = str_replace('<'.$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml); + $objxml = str_replace("<".$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml); return($objxml); } @@ -267,16 +267,16 @@ class dfrn2 { logger("Processing mails"); $msg = array(); - $msg["uid"] = $importer['importer_uid']; - $msg["from-name"] = $xpath->query('dfrn:sender/dfrn:name/text()', $mail)->item(0)->nodeValue; - $msg["from-url"] = $xpath->query('dfrn:sender/dfrn:uri/text()', $mail)->item(0)->nodeValue; - $msg["from-photo"] = $xpath->query('dfrn:sender/dfrn:avatar/text()', $mail)->item(0)->nodeValue; + $msg["uid"] = $importer["importer_uid"]; + $msg["from-name"] = $xpath->query("dfrn:sender/dfrn:name/text()", $mail)->item(0)->nodeValue; + $msg["from-url"] = $xpath->query("dfrn:sender/dfrn:uri/text()", $mail)->item(0)->nodeValue; + $msg["from-photo"] = $xpath->query("dfrn:sender/dfrn:avatar/text()", $mail)->item(0)->nodeValue; $msg["contact-id"] = $importer["id"]; - $msg["uri"] = $xpath->query('dfrn:id/text()', $mail)->item(0)->nodeValue; - $msg["parent-uri"] = $xpath->query('dfrn:in-reply-to/text()', $mail)->item(0)->nodeValue; - $msg["created"] = $xpath->query('dfrn:sentdate/text()', $mail)->item(0)->nodeValue; - $msg["title"] = $xpath->query('dfrn:subject/text()', $mail)->item(0)->nodeValue; - $msg["body"] = $xpath->query('dfrn:content/text()', $mail)->item(0)->nodeValue; + $msg["uri"] = $xpath->query("dfrn:id/text()", $mail)->item(0)->nodeValue; + $msg["parent-uri"] = $xpath->query("dfrn:in-reply-to/text()", $mail)->item(0)->nodeValue; + $msg["created"] = $xpath->query("dfrn:sentdate/text()", $mail)->item(0)->nodeValue; + $msg["title"] = $xpath->query("dfrn:subject/text()", $mail)->item(0)->nodeValue; + $msg["body"] = $xpath->query("dfrn:content/text()", $mail)->item(0)->nodeValue; $msg["seen"] = 0; $msg["replied"] = 0; @@ -287,18 +287,18 @@ class dfrn2 { // send notifications. $notif_params = array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $msg, - 'source_name' => $msg['from-name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' + "type" => NOTIFY_MAIL, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $msg, + "source_name" => $msg["from-name"], + "source_link" => $importer["url"], + "source_photo" => $importer["thumb"], + "verb" => ACTIVITY_POST, + "otype" => "mail" ); notification($notif_params); @@ -311,11 +311,11 @@ class dfrn2 { $suggest = array(); $suggest["uid"] = $importer["importer_uid"]; $suggest["cid"] = $importer["id"]; - $suggest["url"] = $xpath->query('dfrn:url/text()', $suggestion)->item(0)->nodeValue; - $suggest["name"] = $xpath->query('dfrn:name/text()', $suggestion)->item(0)->nodeValue; - $suggest["photo"] = $xpath->query('dfrn:photo/text()', $suggestion)->item(0)->nodeValue; - $suggest["request"] = $xpath->query('dfrn:request/text()', $suggestion)->item(0)->nodeValue; - $suggest["body"] = $xpath->query('dfrn:note/text()', $suggestion)->item(0)->nodeValue; + $suggest["url"] = $xpath->query("dfrn:url/text()", $suggestion)->item(0)->nodeValue; + $suggest["name"] = $xpath->query("dfrn:name/text()", $suggestion)->item(0)->nodeValue; + $suggest["photo"] = $xpath->query("dfrn:photo/text()", $suggestion)->item(0)->nodeValue; + $suggest["request"] = $xpath->query("dfrn:request/text()", $suggestion)->item(0)->nodeValue; + $suggest["body"] = $xpath->query("dfrn:note/text()", $suggestion)->item(0)->nodeValue; // Does our member already have a friend matching this description? @@ -379,19 +379,19 @@ class dfrn2 { ); notification(array( - 'type' => NOTIFY_SUGGEST, - 'notify_flags' => $importer["notify-flags"], - 'language' => $importer["language"], - 'to_name' => $importer["username"], - 'to_email' => $importer["email"], - 'uid' => $importer["importer_uid"], - 'item' => $suggest, - 'link' => App::get_baseurl()."/notifications/intros", - 'source_name' => $importer["name"], - 'source_link' => $importer["url"], - 'source_photo' => $importer["photo"], - 'verb' => ACTIVITY_REQ_FRIEND, - 'otype' => "intro" + "type" => NOTIFY_SUGGEST, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $suggest, + "link" => App::get_baseurl()."/notifications/intros", + "source_name" => $importer["name"], + "source_link" => $importer["url"], + "source_photo" => $importer["photo"], + "verb" => ACTIVITY_REQ_FRIEND, + "otype" => "intro" )); return true; @@ -405,16 +405,16 @@ class dfrn2 { $relocate = array(); $relocate["uid"] = $importer["importer_uid"]; $relocate["cid"] = $importer["id"]; - $relocate["url"] = $xpath->query('dfrn:url/text()', $relocation)->item(0)->nodeValue; - $relocate["name"] = $xpath->query('dfrn:name/text()', $relocation)->item(0)->nodeValue; - $relocate["photo"] = $xpath->query('dfrn:photo/text()', $relocation)->item(0)->nodeValue; - $relocate["thumb"] = $xpath->query('dfrn:thumb/text()', $relocation)->item(0)->nodeValue; - $relocate["micro"] = $xpath->query('dfrn:micro/text()', $relocation)->item(0)->nodeValue; - $relocate["request"] = $xpath->query('dfrn:request/text()', $relocation)->item(0)->nodeValue; - $relocate["confirm"] = $xpath->query('dfrn:confirm/text()', $relocation)->item(0)->nodeValue; - $relocate["notify"] = $xpath->query('dfrn:notify/text()', $relocation)->item(0)->nodeValue; - $relocate["poll"] = $xpath->query('dfrn:poll/text()', $relocation)->item(0)->nodeValue; - $relocate["sitepubkey"] = $xpath->query('dfrn:sitepubkey/text()', $relocation)->item(0)->nodeValue; + $relocate["url"] = $xpath->query("dfrn:url/text()", $relocation)->item(0)->nodeValue; + $relocate["name"] = $xpath->query("dfrn:name/text()", $relocation)->item(0)->nodeValue; + $relocate["photo"] = $xpath->query("dfrn:photo/text()", $relocation)->item(0)->nodeValue; + $relocate["thumb"] = $xpath->query("dfrn:thumb/text()", $relocation)->item(0)->nodeValue; + $relocate["micro"] = $xpath->query("dfrn:micro/text()", $relocation)->item(0)->nodeValue; + $relocate["request"] = $xpath->query("dfrn:request/text()", $relocation)->item(0)->nodeValue; + $relocate["confirm"] = $xpath->query("dfrn:confirm/text()", $relocation)->item(0)->nodeValue; + $relocate["notify"] = $xpath->query("dfrn:notify/text()", $relocation)->item(0)->nodeValue; + $relocate["poll"] = $xpath->query("dfrn:poll/text()", $relocation)->item(0)->nodeValue; + $relocate["sitepubkey"] = $xpath->query("dfrn:sitepubkey/text()", $relocation)->item(0)->nodeValue; // update contact $r = q("SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d;", @@ -479,60 +479,60 @@ class dfrn2 { return true; } - private function upate_content($current, $item, $importer, $entrytype) { + private function update_content($current, $item, $importer, $entrytype) { if (edited_timestamp_is_newer($current, $item)) { // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$item['edited']) < $current['edited']) + if(datetime_convert("UTC","UTC",$item["edited"]) < $current["edited"]) return; $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($item['title']), - dbesc($item['body']), - dbesc($item['tag']), - dbesc(datetime_convert('UTC','UTC',$item['edited'])), + dbesc($item["title"]), + dbesc($item["body"]), + dbesc($item["tag"]), + dbesc(datetime_convert("UTC","UTC",$item["edited"])), dbesc(datetime_convert()), dbesc($item["uri"]), - intval($importer['importer_uid']) + intval($importer["importer_uid"]) ); - create_tags_from_itemuri($item["uri"], $importer['importer_uid']); - update_thread_uri($item["uri"], $importer['importer_uid']); + create_tags_from_itemuri($item["uri"], $importer["importer_uid"]); + update_thread_uri($item["uri"], $importer["importer_uid"]); if ($entrytype == DFRN_REPLY_RC) - proc_run('php',"include/notifier.php","comment-import",$current["id"]); + proc_run("php", "include/notifier.php","comment-import", $current["id"]); } // update last-child if it changes - if($item["last-child"] AND ($item["last-child"] != $current['last-child'])) { + if($item["last-child"] AND ($item["last-child"] != $current["last-child"])) { $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", dbesc(datetime_convert()), dbesc($item["parent-uri"]), - intval($importer['importer_uid']) + intval($importer["importer_uid"]) ); $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", intval($item["last-child"]), dbesc(datetime_convert()), dbesc($item["uri"]), - intval($importer['importer_uid']) + intval($importer["importer_uid"]) ); } } - private function get_entry_type($is_reply, $importer, $item) { - if ($is_reply) { + private function get_entry_type($importer, $item) { + if ($item["parent-uri"] != $item["uri"]) { $community = false; - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP) { - $sql_extra = ''; + if($importer["page-flags"] == PAGE_COMMUNITY || $importer["page-flags"] == PAGE_PRVGROUP) { + $sql_extra = ""; $community = true; - logger('possible community reply'); + logger("possible community action"); } else - $sql_extra = " and contact.self = 1 and item.wall = 1 "; + $sql_extra = " AND `contact`.`self` AND `item`.`wall` "; - // was the top-level post for this reply written by somebody on this site? + // was the top-level post for this action written by somebody on this site? // Specifically, the recipient? - $is_a_remote_comment = false; + $is_a_remote_action = false; $r = q("SELECT `item`.`parent-uri` FROM `item` WHERE `item`.`uri` = '%s' @@ -546,37 +546,81 @@ class dfrn2 { AND `item`.`uid` = %d $sql_extra LIMIT 1", - dbesc($r[0]['parent-uri']), - dbesc($r[0]['parent-uri']), - dbesc($r[0]['parent-uri']), - intval($importer['importer_uid']) + dbesc($r[0]["parent-uri"]), + dbesc($r[0]["parent-uri"]), + dbesc($r[0]["parent-uri"]), + intval($importer["importer_uid"]) ); if($r && count($r)) - $is_a_remote_comment = true; + $is_a_remote_action = true; } - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. + // Does this have the characteristics of a community or private group action? + // If it's an action to a wall post on a community/prvgroup page it's a + // valid community action. Also forum_mode makes it valid for sure. // If neither, it's not. - if($is_a_remote_comment && $community) { - if((!$r[0]['forum_mode']) && (!$r[0]['wall'])) { - $is_a_remote_comment = false; - logger('not a community reply'); + if($is_a_remote_action && $community) { + if((!$r[0]["forum_mode"]) && (!$r[0]["wall"])) { + $is_a_remote_action = false; + logger("not a community action"); } } - } else { - $is_reply = false; - $is_a_remote_comment = false; - } - if ($is_a_remote_comment) - return DFRN_REPLY_RC; - elseif ($is_reply) - return DFRN_REPLY; - else + if ($is_a_remote_action) + return DFRN_REPLY_RC; + else + return DFRN_REPLY; + + } else return DFRN_TOP_LEVEL; + + } + + private function do_poke($item, $importer, $posted_id) { + $verb = urldecode(substr($item["verb"],strpos($item["verb"], "#")+1)); + if(!$verb) + return; + $xo = parse_xml_string($item["object"],false); + + if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { + + // somebody was poked/prodded. Was it me? + $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); + + foreach($links->link as $l) { + $atts = $l->attributes(); + switch($atts["rel"]) { + case "alternate": + $Blink = $atts["href"]; + break; + default: + break; + } + } + if($Blink && link_compare($Blink,$a->get_baseurl()."/profile/".$importer["nickname"])) { + + // send a notification + notification(array( + "type" => NOTIFY_POKE, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $item, + "link" => $a->get_baseurl()."/display/".urlencode(get_item_guid($posted_id)), + "source_name" => stripslashes($item["author-name"]), + "source_link" => $item["author-link"], + "source_photo" => ((link_compare($item["author-link"],$importer["url"])) + ? $importer["thumb"] : $item["author-avatar"]), + "verb" => $item["verb"], + "otype" => "person", + "activity" => $verb, + "parent" => $item["parent"] + )); + } + } } private function process_entry($header, $xpath, $entry, $importer, $contact) { @@ -586,7 +630,7 @@ class dfrn2 { $item = $header; // Get the uri - $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; + $item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue; // Fetch the owner $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", $contact, true); @@ -614,12 +658,12 @@ class dfrn2 { if (($header["network"] != $author["network"]) AND ($author["network"] != "")) $item["network"] = $author["network"]; - $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + $item["title"] = $xpath->query("atom:title/text()", $entry)->item(0)->nodeValue; - $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; - $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; + $item["created"] = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; + $item["edited"] = $xpath->query("atom:updated/text()", $entry)->item(0)->nodeValue; - $item["body"] = $xpath->query('dfrn:env/text()', $entry)->item(0)->nodeValue; + $item["body"] = $xpath->query("dfrn:env/text()", $entry)->item(0)->nodeValue; $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]); // make sure nobody is trying to sneak some html tags by us $item["body"] = notags(base64url_decode($item["body"])); @@ -629,23 +673,23 @@ class dfrn2 { /// @todo Do we need the old check for HTML elements? // We don't need the content element since "dfrn:env" is always present - //$item["body"] = $xpath->query('atom:content/text()', $entry)->item(0)->nodeValue; + //$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue; - $item["last-child"] = $xpath->query('dfrn:comment-allow/text()', $entry)->item(0)->nodeValue; - $item["location"] = $xpath->query('dfrn:location/text()', $entry)->item(0)->nodeValue; + $item["last-child"] = $xpath->query("dfrn:comment-allow/text()", $entry)->item(0)->nodeValue; + $item["location"] = $xpath->query("dfrn:location/text()", $entry)->item(0)->nodeValue; - $georsspoint = $xpath->query('georss:point', $entry); + $georsspoint = $xpath->query("georss:point", $entry); if ($georsspoint) $item["coord"] = $georsspoint->item(0)->nodeValue; - $item["private"] = $xpath->query('dfrn:private/text()', $entry)->item(0)->nodeValue; + $item["private"] = $xpath->query("dfrn:private/text()", $entry)->item(0)->nodeValue; - $item["extid"] = $xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue; + $item["extid"] = $xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue; - if ($xpath->query('dfrn:extid/text()', $entry)->item(0)->nodeValue == "true") + if ($xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue == "true") $item["bookmark"] = true; - $notice_info = $xpath->query('statusnet:notice_info', $entry); + $notice_info = $xpath->query("statusnet:notice_info", $entry); if ($notice_info AND ($notice_info->length > 0)) { foreach($notice_info->item(0)->attributes AS $attributes) { if ($attributes->name == "source") @@ -653,32 +697,30 @@ class dfrn2 { } } - $item["guid"] = $xpath->query('dfrn:diaspora_guid/text()', $entry)->item(0)->nodeValue; + $item["guid"] = $xpath->query("dfrn:diaspora_guid/text()", $entry)->item(0)->nodeValue; - // We store the data from "dfrn:diaspora_signature" in a later step. See some lines below - $item["dsprsig"] = unxmlify($xpath->query('dfrn:diaspora_signature/text()', $entry)->item(0)->nodeValue); + // We store the data from "dfrn:diaspora_signature" in a different table, this is done in "item_store" + $item["dsprsig"] = unxmlify($xpath->query("dfrn:diaspora_signature/text()", $entry)->item(0)->nodeValue); - $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; + $item["verb"] = $xpath->query("activity:verb/text()", $entry)->item(0)->nodeValue; - if ($xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue != "") - $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; + if ($xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue != "") + $item["object-type"] = $xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue; - // I have the feeling that we don't do anything with this data - $object = $xpath->query('activity:object', $entry)->item(0); + $object = $xpath->query("activity:object", $entry)->item(0); $item["object"] = self::transform_activity($xpath, $object, "object"); - // Could someone explain what this is for? - $target = $xpath->query('activity:target', $entry)->item(0); + $target = $xpath->query("activity:target", $entry)->item(0); $item["target"] = self::transform_activity($xpath, $target, "target"); - $categories = $xpath->query('atom:category', $entry); + $categories = $xpath->query("atom:category", $entry); if ($categories) { foreach ($categories AS $category) { foreach($category->attributes AS $attributes) if ($attributes->name == "term") { $term = $attributes->textContent; if(strlen($item["tag"])) - $item["tag"] .= ','; + $item["tag"] .= ","; $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; } @@ -687,7 +729,7 @@ class dfrn2 { $enclosure = ""; - $links = $xpath->query('atom:link', $entry); + $links = $xpath->query("atom:link", $entry); if ($links) { $rel = ""; $href = ""; @@ -715,7 +757,7 @@ class dfrn2 { case "enclosure": $enclosure = $href; if(strlen($item["attach"])) - $item["attach"] .= ','; + $item["attach"] .= ","; $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; break; @@ -726,21 +768,22 @@ class dfrn2 { // Is it a reply or a top level posting? $item["parent-uri"] = $item["uri"]; - $inreplyto = $xpath->query('thr:in-reply-to', $entry); + $inreplyto = $xpath->query("thr:in-reply-to", $entry); if (is_object($inreplyto->item(0))) foreach($inreplyto->item(0)->attributes AS $attributes) if ($attributes->name == "ref") $item["parent-uri"] = $attributes->textContent; - $entrytype = get_entry_type(( $item["parent-uri"] != $item["uri"]), $importer, $item); + // Get the type of the item (Top level post, reply or remote reply) + $entrytype = get_entry_type($importer, $item); // Now assign the rest of the values that depend on the type of the message if ($entrytype == DFRN_REPLY_RC) { if (!isset($item["object-type"])) $item["object-type"] = ACTIVITY_OBJ_COMMENT; - $item["type"] = 'remote-comment'; - $item['wall'] = 1; + $item["type"] = "remote-comment"; + $item["wall"] = 1; } elseif ($entrytype == DFRN_REPLY) { if (!isset($item["object-type"])) $item["object-type"] = ACTIVITY_OBJ_COMMENT; @@ -749,21 +792,21 @@ class dfrn2 { $item["object-type"] = ACTIVITY_OBJ_NOTE; if ($item["object-type"] === ACTIVITY_OBJ_EVENT) { - $ev = bbtoevent($item['body']); - if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { - $ev['cid'] = $importer['id']; - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item["uri"]; - $ev['edited'] = $item['edited']; + $ev = bbtoevent($item["body"]); + if((x($ev, "desc") || x($ev, "summary")) && x($ev, "start")) { + $ev["cid"] = $importer["id"]; + $ev["uid"] = $importer["uid"]; + $ev["uri"] = $item["uri"]; + $ev["edited"] = $item["edited"]; $ev['private'] = $item['private']; - $ev['guid'] = $item['guid']; + $ev["guid"] = $item["guid"]; - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + $r = q("SELECT `id` FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item["uri"]), - intval($importer['uid']) + intval($importer["uid"]) ); if(count($r)) - $ev['id'] = $r[0]['id']; + $ev["id"] = $r[0]["id"]; $xyz = event_store($ev); return; } @@ -772,43 +815,43 @@ class dfrn2 { $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item["uri"]), - intval($importer['importer_uid']) + intval($importer["importer_uid"]) ); // Update content if 'updated' changes if(count($r)) { - self::upate_content($r[0], $item, $importer, $entrytype); + self::update_content($r[0], $item, $importer, $entrytype); return; } if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { - if($importer['rel'] == CONTACT_IS_FOLLOWER) + if($importer["rel"] == CONTACT_IS_FOLLOWER) return; - if(($item['verb'] === ACTIVITY_LIKE) - || ($item['verb'] === ACTIVITY_DISLIKE) - || ($item['verb'] === ACTIVITY_ATTEND) - || ($item['verb'] === ACTIVITY_ATTENDNO) - || ($item['verb'] === ACTIVITY_ATTENDMAYBE)) { + if(($item["verb"] === ACTIVITY_LIKE) + || ($item["verb"] === ACTIVITY_DISLIKE) + || ($item["verb"] === ACTIVITY_ATTEND) + || ($item["verb"] === ACTIVITY_ATTENDNO) + || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) { $is_like = true; - $item['type'] = 'activity'; - $item['gravity'] = GRAVITY_LIKE; + $item["type"] = "activity"; + $item["gravity"] = GRAVITY_LIKE; // only one like or dislike per person // splitted into two queries for performance issues $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($item['uid']), - dbesc($item['author-link']), - dbesc($item['verb']), - dbesc($item['parent-uri']) + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) ); if($r && count($r)) return; $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($item['uid']), - dbesc($item['author-link']), - dbesc($item['verb']), - dbesc($item['parent-uri']) + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) ); if($r && count($r)) return; @@ -816,15 +859,15 @@ class dfrn2 { } else $is_like = false; - if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) { + if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item['object'],false); - $xt = parse_xml_string($item['target'],false); + $xo = parse_xml_string($item["object"],false); + $xt = parse_xml_string($item["target"],false); if($xt->type == ACTIVITY_OBJ_NOTE) { $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($xt->id), - intval($importer['importer_uid']) + intval($importer["importer_uid"]) ); if(!count($r)) @@ -832,12 +875,12 @@ class dfrn2 { // extract tag, if not duplicate, add to parent item if($xo->content) { - if(!(stristr($r[0]['tag'],trim($xo->content)))) { + if(!(stristr($r[0]["tag"],trim($xo->content)))) { q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", - dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), - intval($r[0]['id']) + dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), + intval($r[0]["id"]) ); - create_tags_from_item($r[0]['id']); + create_tags_from_item($r[0]["id"]); } } } @@ -852,46 +895,46 @@ class dfrn2 { $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($posted_id), - intval($importer['importer_uid']) + intval($importer["importer_uid"]) ); if(count($r)) { - $parent = $r[0]['parent']; - $parent_uri = $r[0]['parent-uri']; + $parent = $r[0]["parent"]; + $parent_uri = $r[0]["parent-uri"]; } if(!$is_like) { $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($r[0]['parent']) + intval($importer["importer_uid"]), + intval($r[0]["parent"]) ); $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", dbesc(datetime_convert()), - intval($importer['importer_uid']), + intval($importer["importer_uid"]), intval($posted_id) ); } if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) { - proc_run('php',"include/notifier.php","comment-import","$posted_id"); + proc_run("php", "include/notifier.php", "comment-import", $posted_id); } return true; } } else { - if(!link_compare($item['owner-link'],$importer['url'])) { + if(!link_compare($item["owner-link"],$importer["url"])) { // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, // but otherwise there's a possible data mixup on the sender's system. // the tgroup delivery code called from item_store will correct it if it's a forum, // but we're going to unconditionally correct it here so that the post will always be owned by our contact. logger('Correcting item owner.', LOGGER_DEBUG); - $item['owner-name'] = $importer['senderName']; - $item['owner-link'] = $importer['url']; - $item['owner-avatar'] = $importer['thumb']; + $item["owner-name"] = $importer["senderName"]; + $item["owner-link"] = $importer["url"]; + $item["owner-avatar"] = $importer["thumb"]; } - if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer['importer_uid'],$item))) + if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) return; // This is my contact on another system, but it's really me. @@ -900,53 +943,8 @@ class dfrn2 { $posted_id = item_store($item, false, $notify); - if(stristr($item['verb'],ACTIVITY_POKE)) { - $verb = urldecode(substr($item['verb'],strpos($item['verb'],'#')+1)); - if(!$verb) - return; - $xo = parse_xml_string($item['object'],false); - - if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { - - // somebody was poked/prodded. Was it me? - $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); - - foreach($links->link as $l) { - $atts = $l->attributes(); - switch($atts['rel']) { - case "alternate": - $Blink = $atts['href']; - break; - default: - break; - } - } - if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) { - - // send a notification - require_once('include/enotify.php'); - - notification(array( - 'type' => NOTIFY_POKE, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $item, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)), - 'source_name' => stripslashes($item['author-name']), - 'source_link' => $item['author-link'], - 'source_photo' => ((link_compare($item['author-link'],$importer['url'])) - ? $importer['thumb'] : $item['author-avatar']), - 'verb' => $item['verb'], - 'otype' => 'person', - 'activity' => $verb, - 'parent' => $item['parent'] - )); - } - } - } + if(stristr($item["verb"],ACTIVITY_POKE)) + self::do_poke($item, $importer, $posted_id); } } @@ -961,84 +959,31 @@ class dfrn2 { $when = $attributes->textContent; } if ($when) - $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); + $when = datetime_convert("UTC", "UTC", $when, "Y-m-d H:i:s"); else - $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); + $when = datetime_convert("UTC", "UTC", "now", "Y-m-d H:i:s"); if (!$uri OR !$contact_id) return false; - - $is_reply = false; - $r = q("SELECT `id`, `parent-uri`, `parent` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($uri), - intval($importer['importer_uid']) - ); - if(count($r)) { - $parent_uri = $r[0]['parent-uri']; - if($r[0]['id'] != $r[0]['parent']) - $is_reply = true; - } - - if($is_reply) { - $community = false; - - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP) { - $sql_extra = ''; - $community = true; - logger('possible community delete'); - } else - $sql_extra = " AND `contact`.`self` AND `item`.`wall`"; - - // was the top-level post for this reply written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_delete = false; - - $r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item` - INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($parent_uri), - dbesc($parent_uri), - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - if($r && count($r)) - $is_a_remote_delete = true; - - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_delete && $community) { - if((!$r[0]['forum_mode']) && (!$r[0]['wall'])) { - $is_a_remote_delete = false; - logger('not a community delete'); - } - } - - if($is_a_remote_delete) { - logger('received remote delete'); - } - } - + /// @todo Only select the used fields $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", dbesc($uri), intval($importer["uid"]), intval($contact_id) ); - if(!count($r)) + if(!count($r)) { logger("Item with uri ".$uri." from contact ".$contact_id." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG); - else { + return; + } else { + $item = $r[0]; + $entrytype = get_entry_type($importer, $item); + if(!$item["deleted"]) - logger('deleting item '.$item["id"].' uri='.$item['uri'], LOGGER_DEBUG); + logger('deleting item '.$item["id"].' uri='.$uri, LOGGER_DEBUG); else return; @@ -1059,14 +1004,14 @@ class dfrn2 { // For tags, the owner cannot remove the tag on the author's copy of the post. - $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); - $author_remove = (($item['origin'] && $item['self']) ? true : false); - $author_copy = (($item['origin']) ? true : false); + $owner_remove = (($item["contact-id"] == $i[0]["contact-id"]) ? true: false); + $author_remove = (($item["origin"] && $item["self"]) ? true : false); + $author_copy = (($item["origin"]) ? true : false); if($owner_remove && $author_copy) return; if($author_remove || $owner_remove) { - $tags = explode(',',$i[0]['tag']); + $tags = explode(',',$i[0]["tag"]); $newtags = array(); if(count($tags)) { foreach($tags as $tag) @@ -1075,26 +1020,26 @@ class dfrn2 { } q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", dbesc(implode(',',$newtags)), - intval($i[0]['id']) + intval($i[0]["id"]) ); - create_tags_from_item($i[0]['id']); + create_tags_from_item($i[0]["id"]); } } } } - if($item['uri'] == $item['parent-uri']) { + if($entrytype == DFRN_TOP_LEVEL) { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '', `title` = '' WHERE `parent-uri` = '%s' AND `uid` = %d", dbesc($when), dbesc(datetime_convert()), - dbesc($item['uri']), - intval($importer['uid']) + dbesc($uri), + intval($importer["uid"]) ); - create_tags_from_itemuri($item['uri'], $importer['uid']); - create_files_from_itemuri($item['uri'], $importer['uid']); - update_thread_uri($item['uri'], $importer['uid']); + create_tags_from_itemuri($uri, $importer["uid"]); + create_files_from_itemuri($uri, $importer["uid"]); + update_thread_uri($uri, $importer["uid"]); } else { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '', `title` = '' @@ -1102,34 +1047,34 @@ class dfrn2 { dbesc($when), dbesc(datetime_convert()), dbesc($uri), - intval($importer['uid']) + intval($importer["uid"]) ); - create_tags_from_itemuri($uri, $importer['uid']); - create_files_from_itemuri($uri, $importer['uid']); - update_thread_uri($uri, $importer['importer_uid']); - if($item['last-child']) { + create_tags_from_itemuri($uri, $importer["uid"]); + create_files_from_itemuri($uri, $importer["uid"]); + update_thread_uri($uri, $importer["importer_uid"]); + if($item["last-child"]) { // ensure that last-child is set in case the comment that had it just got wiped. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", dbesc(datetime_convert()), - dbesc($item['parent-uri']), - intval($item['uid']) + dbesc($item["parent-uri"]), + intval($item["uid"]) ); // who is the last child now? $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d ORDER BY `created` DESC LIMIT 1", - dbesc($item['parent-uri']), - intval($importer['uid']) + dbesc($item["parent-uri"]), + intval($importer["uid"]) ); if(count($r)) { q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", - intval($r[0]['id']) + intval($r[0]["id"]) ); } } // if this is a relayed delete, propagate it to other recipients - if($is_a_remote_delete) - proc_run('php',"include/notifier.php","drop",$item['id']); + if($entrytype == DFRN_REPLY_RC) + proc_run("php", "include/notifier.php","drop", $item["id"]); } } } @@ -1143,17 +1088,18 @@ class dfrn2 { @$doc->loadXML($xml); $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', NAMESPACE_ATOM1); - $xpath->registerNamespace('thr', NAMESPACE_THREAD); - $xpath->registerNamespace('at', NAMESPACE_TOMB); - $xpath->registerNamespace('media', NAMESPACE_MEDIA); - $xpath->registerNamespace('dfrn', NAMESPACE_DFRN); - $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); - $xpath->registerNamespace('georss', NAMESPACE_GEORSS); - $xpath->registerNamespace('poco', NAMESPACE_POCO); - $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); - $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); + $xpath->registerNamespace("atom", NAMESPACE_ATOM1); + $xpath->registerNamespace("thr", NAMESPACE_THREAD); + $xpath->registerNamespace("at", NAMESPACE_TOMB); + $xpath->registerNamespace("media", NAMESPACE_MEDIA); + $xpath->registerNamespace("dfrn", NAMESPACE_DFRN); + $xpath->registerNamespace("activity", NAMESPACE_ACTIVITY); + $xpath->registerNamespace("georss", NAMESPACE_GEORSS); + $xpath->registerNamespace("poco", NAMESPACE_POCO); + $xpath->registerNamespace("ostatus", NAMESPACE_OSTATUS); + $xpath->registerNamespace("statusnet", NAMESPACE_STATUSNET); + /// @todo Do we need this? if (!$contact) { $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `self`", intval($importer["uid"])); $contact = $r[0]; @@ -1179,7 +1125,7 @@ class dfrn2 { //} // is it a public forum? Private forums aren't supported by now with this method - $forum = intval($xpath->evaluate('/atom:feed/dfrn:community/text()', $context)->item(0)->nodeValue); + $forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue); if ($forum AND ($dfrn_owner["contact-id"] != 0)) q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d", @@ -1187,23 +1133,23 @@ class dfrn2 { intval($dfrn_owner["contact-id"]) ); - $mails = $xpath->query('/atom:feed/dfrn:mail'); + $mails = $xpath->query("/atom:feed/dfrn:mail"); foreach ($mails AS $mail) self::process_mail($xpath, $mail, $importer); - $suggestions = $xpath->query('/atom:feed/dfrn:suggest'); + $suggestions = $xpath->query("/atom:feed/dfrn:suggest"); foreach ($suggestions AS $suggestion) self::process_suggestion($xpath, $suggestion, $importer); - $relocations = $xpath->query('/atom:feed/dfrn:relocate'); + $relocations = $xpath->query("/atom:feed/dfrn:relocate"); foreach ($relocations AS $relocation) self::process_relocation($xpath, $relocation, $importer); - $deletions = $xpath->query('/atom:feed/at:deleted-entry'); + $deletions = $xpath->query("/atom:feed/at:deleted-entry"); foreach ($deletions AS $deletion) self::process_deletion($header, $xpath, $deletion, $importer, $dfrn_owner["contact-id"]); - $entries = $xpath->query('/atom:feed/atom:entry'); + $entries = $xpath->query("/atom:feed/atom:entry"); foreach ($entries AS $entry) self::process_entry($header, $xpath, $entry, $importer, $contact); } From 24d471620404e56105671b00f32738881e5af24b Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Tue, 2 Feb 2016 15:56:06 +0100 Subject: [PATCH 014/273] perfect-scrollbar: update to 0.6.10 --- js/main.js | 5 +- .../perfect-scrollbar/perfect-scrollbar.css | 51 ++++---- .../perfect-scrollbar.jquery.js | 109 ++++++------------ .../perfect-scrollbar.min.css | 4 +- 4 files changed, 65 insertions(+), 104 deletions(-) diff --git a/js/main.js b/js/main.js index d1fd3765fb..bba5e92969 100644 --- a/js/main.js +++ b/js/main.js @@ -170,9 +170,8 @@ var notifications_mark = unescape($('<div>').append( $("#nav-notifications-mark-all").clone() ).html()); //outerHtml hack var notifications_empty = unescape($("#nav-notifications-menu").html()); - /* enable perfect-scrollbars for nav-notivications-menu */ - $('#nav-notifications-menu').perfectScrollbar(); - $('aside').perfectScrollbar(); + /* enable perfect-scrollbars for different elements */ + $('#nav-notifications-menu, aside').perfectScrollbar(); /* nav update event */ $('nav').bind('nav-update', function(e,data){ diff --git a/library/perfect-scrollbar/perfect-scrollbar.css b/library/perfect-scrollbar/perfect-scrollbar.css index 32cf99b0a3..354705dd57 100644 --- a/library/perfect-scrollbar/perfect-scrollbar.css +++ b/library/perfect-scrollbar/perfect-scrollbar.css @@ -1,10 +1,19 @@ -/* perfect-scrollbar v0.6.8 */ +/* perfect-scrollbar v0.6.10 */ .ps-container { -ms-touch-action: none; - overflow: hidden !important; } + touch-action: none; + overflow: hidden !important; + -ms-overflow-style: none; } + @supports (-ms-overflow-style: none) { + .ps-container { + overflow: auto !important; } } + @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .ps-container { + overflow: auto !important; } } .ps-container.ps-active-x > .ps-scrollbar-x-rail, .ps-container.ps-active-y > .ps-scrollbar-y-rail { - display: block; } + display: block; + background-color: transparent; } .ps-container.ps-in-scrolling { pointer-events: none; } .ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail { @@ -23,13 +32,12 @@ /* please don't change 'position' */ -webkit-border-radius: 4px; -moz-border-radius: 4px; - -ms-border-radius: 4px; border-radius: 4px; opacity: 0; - -webkit-transition: background-color 0.2s linear, opacity 0.2s linear; - -moz-transition: background-color 0.2s linear, opacity 0.2s linear; - -o-transition: background-color 0.2s linear, opacity 0.2s linear; - transition: background-color 0.2s linear, opacity 0.2s linear; + -webkit-transition: background-color .2s linear, opacity .2s linear; + -moz-transition: background-color .2s linear, opacity .2s linear; + -o-transition: background-color .2s linear, opacity .2s linear; + transition: background-color .2s linear, opacity .2s linear; bottom: 3px; /* there must be 'bottom' for ps-scrollbar-x-rail */ height: 8px; } @@ -39,12 +47,11 @@ background-color: #aaa; -webkit-border-radius: 4px; -moz-border-radius: 4px; - -ms-border-radius: 4px; border-radius: 4px; - -webkit-transition: background-color 0.2s linear; - -moz-transition: background-color 0.2s linear; - -o-transition: background-color 0.2s linear; - transition: background-color 0.2s linear; + -webkit-transition: background-color .2s linear; + -moz-transition: background-color .2s linear; + -o-transition: background-color .2s linear; + transition: background-color .2s linear; bottom: 0; /* there must be 'bottom' for ps-scrollbar-x */ height: 8px; } @@ -54,13 +61,12 @@ /* please don't change 'position' */ -webkit-border-radius: 4px; -moz-border-radius: 4px; - -ms-border-radius: 4px; border-radius: 4px; opacity: 0; - -webkit-transition: background-color 0.2s linear, opacity 0.2s linear; - -moz-transition: background-color 0.2s linear, opacity 0.2s linear; - -o-transition: background-color 0.2s linear, opacity 0.2s linear; - transition: background-color 0.2s linear, opacity 0.2s linear; + -webkit-transition: background-color .2s linear, opacity .2s linear; + -moz-transition: background-color .2s linear, opacity .2s linear; + -o-transition: background-color .2s linear, opacity .2s linear; + transition: background-color .2s linear, opacity .2s linear; right: 3px; /* there must be 'right' for ps-scrollbar-y-rail */ width: 8px; } @@ -70,12 +76,11 @@ background-color: #aaa; -webkit-border-radius: 4px; -moz-border-radius: 4px; - -ms-border-radius: 4px; border-radius: 4px; - -webkit-transition: background-color 0.2s linear; - -moz-transition: background-color 0.2s linear; - -o-transition: background-color 0.2s linear; - transition: background-color 0.2s linear; + -webkit-transition: background-color .2s linear; + -moz-transition: background-color .2s linear; + -o-transition: background-color .2s linear; + transition: background-color .2s linear; right: 0; /* there must be 'right' for ps-scrollbar-y */ width: 8px; } diff --git a/library/perfect-scrollbar/perfect-scrollbar.jquery.js b/library/perfect-scrollbar/perfect-scrollbar.jquery.js index 2bc3b2f939..d94ed91bcd 100644 --- a/library/perfect-scrollbar/perfect-scrollbar.jquery.js +++ b/library/perfect-scrollbar/perfect-scrollbar.jquery.js @@ -1,10 +1,12 @@ -/* perfect-scrollbar v0.6.8 */ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors +/* perfect-scrollbar v0.6.10 + * + * Copyright (c) 2015 Hyunje Alex Jun and other contributors * Licensed under the MIT License * * Source: https://github.com/noraesae/perfect-scrollbar */ + +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 'use strict'; var ps = require('../main') @@ -50,9 +52,6 @@ if (typeof define === 'function' && define.amd) { module.exports = mountJQuery; },{"../main":7,"../plugin/instances":18}],2:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; function oldAdd(element, className) { @@ -97,9 +96,6 @@ exports.list = function (element) { }; },{}],3:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var DOM = {}; @@ -186,9 +182,6 @@ DOM.queryChildren = function (element, selector) { module.exports = DOM; },{}],4:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var EventElement = function (element) { @@ -262,9 +255,6 @@ EventManager.prototype.once = function (element, eventName, handler) { module.exports = EventManager; },{}],5:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; module.exports = (function () { @@ -280,9 +270,6 @@ module.exports = (function () { })(); },{}],6:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var cls = require('./class') @@ -366,9 +353,6 @@ exports.env = { }; },{"./class":2,"./dom":3}],7:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var destroy = require('./plugin/destroy') @@ -382,9 +366,6 @@ module.exports = { }; },{"./plugin/destroy":9,"./plugin/initialize":17,"./plugin/update":21}],8:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; module.exports = { @@ -400,13 +381,11 @@ module.exports = { useKeyboard: true, useSelectionScroll: false, wheelPropagation: false, - wheelSpeed: 1 + wheelSpeed: 1, + theme: 'default' }; },{}],9:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var d = require('../lib/dom') @@ -431,9 +410,6 @@ module.exports = function (element) { }; },{"../lib/dom":3,"../lib/helper":6,"./instances":18}],10:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var h = require('../../lib/helper') @@ -496,9 +472,6 @@ module.exports = function (element) { }; },{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var d = require('../../lib/dom') @@ -604,12 +577,10 @@ module.exports = function (element) { }; },{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var h = require('../../lib/helper') + , d = require('../../lib/dom') , instances = require('../instances') , updateGeometry = require('../update-geometry') , updateScroll = require('../update-scroll'); @@ -652,7 +623,10 @@ function bindKeyboardHandler(element, i) { return; } - if (!hovered) { + var focused = d.matches(i.scrollbarX, ':focus') || + d.matches(i.scrollbarY, ':focus'); + + if (!hovered && !focused) { return; } @@ -730,10 +704,7 @@ module.exports = function (element) { bindKeyboardHandler(element, i); }; -},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ +},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(require,module,exports){ 'use strict'; var instances = require('../instances') @@ -870,9 +841,6 @@ module.exports = function (element) { }; },{"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var instances = require('../instances') @@ -890,9 +858,6 @@ module.exports = function (element) { }; },{"../instances":18,"../update-geometry":19}],15:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var h = require('../../lib/helper') @@ -1004,9 +969,6 @@ module.exports = function (element) { }; },{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var instances = require('../instances') @@ -1177,9 +1139,6 @@ module.exports = function (element, supportsTouch, supportsIePointer) { }; },{"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var cls = require('../lib/class') @@ -1205,6 +1164,7 @@ module.exports = function (element, userSettings) { var i = instances.add(element); i.settings = h.extend(i.settings, userSettings); + cls.add(element, 'ps-theme-' + i.settings.theme); clickRailHandler(element); dragScrollbarHandler(element); @@ -1226,12 +1186,10 @@ module.exports = function (element, userSettings) { }; },{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; -var d = require('../lib/dom') +var cls = require('../lib/class') + , d = require('../lib/dom') , defaultSettings = require('./default-setting') , EventManager = require('../lib/event-manager') , guid = require('../lib/guid') @@ -1261,9 +1219,19 @@ function Instance(element) { i.event = new EventManager(); i.ownerDocument = element.ownerDocument || document; + function focus() { + cls.add(element, 'ps-focus'); + } + + function blur() { + cls.remove(element, 'ps-focus'); + } + i.scrollbarXRail = d.appendTo(d.e('div', 'ps-scrollbar-x-rail'), element); i.scrollbarX = d.appendTo(d.e('div', 'ps-scrollbar-x'), i.scrollbarXRail); i.scrollbarX.setAttribute('tabindex', 0); + i.event.bind(i.scrollbarX, 'focus', focus); + i.event.bind(i.scrollbarX, 'blur', blur); i.scrollbarXActive = null; i.scrollbarXWidth = null; i.scrollbarXLeft = null; @@ -1281,6 +1249,8 @@ function Instance(element) { i.scrollbarYRail = d.appendTo(d.e('div', 'ps-scrollbar-y-rail'), element); i.scrollbarY = d.appendTo(d.e('div', 'ps-scrollbar-y'), i.scrollbarYRail); i.scrollbarY.setAttribute('tabindex', 0); + i.event.bind(i.scrollbarY, 'focus', focus); + i.event.bind(i.scrollbarY, 'blur', blur); i.scrollbarYActive = null; i.scrollbarYHeight = null; i.scrollbarYTop = null; @@ -1336,10 +1306,7 @@ exports.get = function (element) { return instances[getId(element)]; }; -},{"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ +},{"../lib/class":2,"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(require,module,exports){ 'use strict'; var cls = require('../lib/class') @@ -1468,9 +1435,6 @@ module.exports = function (element) { }; },{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var instances = require('./instances'); @@ -1513,29 +1477,25 @@ module.exports = function (element, axis, value) { } if (axis === 'top' && value <= 0) { - element.scrollTop = 0; + element.scrollTop = value = 0; // don't allow negative scroll element.dispatchEvent(yStartEvent); - return; // don't allow negative scroll } if (axis === 'left' && value <= 0) { - element.scrollLeft = 0; + element.scrollLeft = value = 0; // don't allow negative scroll element.dispatchEvent(xStartEvent); - return; // don't allow negative scroll } var i = instances.get(element); if (axis === 'top' && value >= i.contentHeight - i.containerHeight) { - element.scrollTop = i.contentHeight - i.containerHeight; + element.scrollTop = value = i.contentHeight - i.containerHeight; // don't allow scroll past container element.dispatchEvent(yEndEvent); - return; // don't allow scroll past container } if (axis === 'left' && value >= i.contentWidth - i.containerWidth) { - element.scrollLeft = i.contentWidth - i.containerWidth; + element.scrollLeft = value = i.contentWidth - i.containerWidth; // don't allow scroll past container element.dispatchEvent(xEndEvent); - return; // don't allow scroll past container } if (!lastTop) { @@ -1575,9 +1535,6 @@ module.exports = function (element, axis, value) { }; },{"./instances":18}],21:[function(require,module,exports){ -/* Copyright (c) 2015 Hyunje Alex Jun and other contributors - * Licensed under the MIT License - */ 'use strict'; var d = require('../lib/dom') diff --git a/library/perfect-scrollbar/perfect-scrollbar.min.css b/library/perfect-scrollbar/perfect-scrollbar.min.css index 288671cfcc..e8d2f37d9c 100644 --- a/library/perfect-scrollbar/perfect-scrollbar.min.css +++ b/library/perfect-scrollbar/perfect-scrollbar.min.css @@ -1,2 +1,2 @@ -/* perfect-scrollbar v0.6.8 */ -.ps-container{-ms-touch-action:none;overflow:hidden !important}.ps-container.ps-active-x>.ps-scrollbar-x-rail,.ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block}.ps-container.ps-in-scrolling{pointer-events:none}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:0.9}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:0.9}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}.ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;opacity:0;-webkit-transition:background-color 0.2s linear,opacity 0.2s linear;-moz-transition:background-color 0.2s linear,opacity 0.2s linear;-o-transition:background-color 0.2s linear,opacity 0.2s linear;transition:background-color 0.2s linear,opacity 0.2s linear;bottom:3px;height:8px}.ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;-webkit-transition:background-color 0.2s linear;-moz-transition:background-color 0.2s linear;-o-transition:background-color 0.2s linear;transition:background-color 0.2s linear;bottom:0;height:8px}.ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;opacity:0;-webkit-transition:background-color 0.2s linear,opacity 0.2s linear;-moz-transition:background-color 0.2s linear,opacity 0.2s linear;-o-transition:background-color 0.2s linear,opacity 0.2s linear;transition:background-color 0.2s linear,opacity 0.2s linear;right:3px;width:8px}.ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;-webkit-transition:background-color 0.2s linear;-moz-transition:background-color 0.2s linear;-o-transition:background-color 0.2s linear;transition:background-color 0.2s linear;right:0;width:8px}.ps-container:hover.ps-in-scrolling{pointer-events:none}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:0.9}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:0.9}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}.ps-container:hover>.ps-scrollbar-x-rail,.ps-container:hover>.ps-scrollbar-y-rail{opacity:0.6}.ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:0.9}.ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}.ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:0.9}.ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999} +/* perfect-scrollbar v0.6.10 */ +.ps-container{-ms-touch-action:none;touch-action:none;overflow:hidden !important;-ms-overflow-style:none}@supports (-ms-overflow-style: none){.ps-container{overflow:auto !important}}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.ps-container{overflow:auto !important}}.ps-container.ps-active-x>.ps-scrollbar-x-rail,.ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block;background-color:transparent}.ps-container.ps-in-scrolling{pointer-events:none}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:0.9}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:0.9}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}.ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;bottom:3px;height:8px}.ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-transition:background-color .2s linear;-moz-transition:background-color .2s linear;-o-transition:background-color .2s linear;transition:background-color .2s linear;bottom:0;height:8px}.ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;right:3px;width:8px}.ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-transition:background-color .2s linear;-moz-transition:background-color .2s linear;-o-transition:background-color .2s linear;transition:background-color .2s linear;right:0;width:8px}.ps-container:hover.ps-in-scrolling{pointer-events:none}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:0.9}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:0.9}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999}.ps-container:hover>.ps-scrollbar-x-rail,.ps-container:hover>.ps-scrollbar-y-rail{opacity:0.6}.ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:0.9}.ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}.ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:0.9}.ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999} From f604f13d7da004b52c5858091aeecc678ee54082 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 2 Feb 2016 20:57:19 +0100 Subject: [PATCH 015/273] New method to create a guid if none was given --- include/items.php | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/include/items.php b/include/items.php index 21a0c414dc..ff5d919c9e 100644 --- a/include/items.php +++ b/include/items.php @@ -773,6 +773,18 @@ function item_add_language_opt(&$arr) { } } +function uri_to_guid($uri) { + $parsed = parse_url($uri); + $guid_prefix = hash("crc32", $parsed["host"]); + + unset($parsed["scheme"]); + + $host_id = implode("/", $parsed); + $host_hash = hash("ripemd128", $host_id); + + return $guid_prefix.$host_hash; +} + function item_store($arr,$force_parent = false, $notify = false, $dontcache = false) { // If it is a posting where users should get notifications, then define it as wall posting @@ -848,33 +860,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa } } - // If there is no guid then take the same guid that was taken before for the same uri - if ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "") AND (trim($arr['network']) != "")) { - logger('item_store: checking for an existing guid for uri '.$arr['uri'], LOGGER_DEBUG); - $r = q("SELECT `guid` FROM `guid` WHERE `uri` = '%s' AND `network` = '%s' LIMIT 1", - dbesc(trim($arr['uri'])), dbesc(trim($arr['network']))); - - if(count($r)) { - $arr['guid'] = $r[0]["guid"]; - logger('item_store: found guid '.$arr['guid'].' for uri '.$arr['uri'], LOGGER_DEBUG); - } - } - - // If there is no guid then take the same guid that was taken before for the same plink - if ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "") AND (trim($arr['network']) != "")) { - logger('item_store: checking for an existing guid for plink '.$arr['plink'], LOGGER_DEBUG); - $r = q("SELECT `guid`, `uri` FROM `guid` WHERE `plink` = '%s' AND `network` = '%s' LIMIT 1", - dbesc(trim($arr['plink'])), dbesc(trim($arr['network']))); - - if(count($r)) { - $arr['guid'] = $r[0]["guid"]; - logger('item_store: found guid '.$arr['guid'].' for plink '.$arr['plink'], LOGGER_DEBUG); - - if ($r[0]["uri"] != $arr['uri']) - logger('Different uri for same guid: '.$arr['uri'].' and '.$r[0]["uri"].' - this shouldnt happen!', LOGGER_DEBUG); - } - } - // Shouldn't happen but we want to make absolutely sure it doesn't leak from a plugin. // Deactivated, since the bbcode parser can handle with it - and it destroys posts with some smileys that contain "<" //if((strpos($arr['body'],'<') !== false) || (strpos($arr['body'],'>') !== false)) @@ -884,6 +869,10 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa if ($notify) $guid_prefix = ""; + elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "")) + $arr['guid'] = uri_to_guid($arr['plink']); + elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) + $arr['guid'] = uri_to_guid($arr['uri']); else { $parsed = parse_url($arr["author-link"]); $guid_prefix = hash("crc32", $parsed["host"]); From 58c1f9a60f951b5dad39f33cce5fd7d5e2538ef6 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Wed, 3 Feb 2016 03:59:42 +0100 Subject: [PATCH 016/273] vier: use object-fit: cover for recent photos view --- view/theme/vier/style.css | 42 ++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index 16685e55ef..e0abee837c 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -2947,35 +2947,41 @@ a.mail-list-link { color: #999999; } +/* photo album page */ .photo-top-image-wrapper { - position: relative; - float: left; - margin-top: 15px; - margin-right: 15px; - width: 200px; height: 200px; - overflow: hidden; + position: relative; + float: left; + margin-top: 15px; + margin-right: 15px; + width: 200px; height: 200px; + overflow: hidden; } .photo-top-album-name { - width: 100%; - min-height: 2em; - position: absolute; - bottom: 0px; - padding: 0px 3px; - padding-top: 0.5em; - background-color: rgb(255, 255, 255); + width: 100%; + min-height: 2em; + position: absolute; + bottom: 0px; + padding: 0px 3px; + padding-top: 0.5em; + background-color: rgb(255, 255, 255); } #photo-top-end { - clear: both; + clear: both; } #photo-top-links { - margin-bottom: 30px; - margin-left: 30px; + margin-bottom: 30px; + margin-left: 30px; } #photos-upload-newalbum-div { - float: left; - width: 175px; + float: left; + width: 175px; +} +img.photo-top-photo { + width: 100%; + height: 100%; + object-fit: cover; } .menu-profile-list{ From 47d2e3b1424bdbc903a84ea94b9d3c7dfe4b5c9f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 3 Feb 2016 05:00:26 +0100 Subject: [PATCH 017/273] Added some documentation --- include/items.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/items.php b/include/items.php index ff5d919c9e..c149660fac 100644 --- a/include/items.php +++ b/include/items.php @@ -773,13 +773,25 @@ function item_add_language_opt(&$arr) { } } +/** + * @brief Creates an unique guid out of a given uri + * + * @param string $uri uri of an item entry + * @return string unique guid + */ function uri_to_guid($uri) { + + // Our regular guid routine is using this kind of prefix as well + // We have to avoid that different routines could accidentally create the same value $parsed = parse_url($uri); $guid_prefix = hash("crc32", $parsed["host"]); + // Remove the scheme to make sure that "https" and "http" doesn't make a difference unset($parsed["scheme"]); $host_id = implode("/", $parsed); + + // We could use any hash algorithm since it isn't a security issue $host_hash = hash("ripemd128", $host_id); return $guid_prefix.$host_hash; From 9fcd74f470f596fb85727af561d86404b59dfc93 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Wed, 3 Feb 2016 14:01:37 +0100 Subject: [PATCH 018/273] Pass App class instace to all templates via "$APP" variable --- include/friendica_smarty.php | 2 ++ include/text.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/friendica_smarty.php b/include/friendica_smarty.php index 99dc12bf8d..9ba2d2a744 100644 --- a/include/friendica_smarty.php +++ b/include/friendica_smarty.php @@ -60,6 +60,8 @@ class FriendicaSmartyEngine implements ITemplateEngine { $template = $s; $s = new FriendicaSmarty(); } + + $r['$APP'] = get_app(); // "middleware": inject variables into templates $arr = array( diff --git a/include/text.php b/include/text.php index 4f3af5aee8..c7681a4d58 100644 --- a/include/text.php +++ b/include/text.php @@ -22,7 +22,7 @@ function replace_macros($s,$r) { $a = get_app(); // pass $baseurl to all templates - $r['$baseurl'] = z_root(); + $r['$baseurl'] = $a->get_baseurl(); $t = $a->template_engine(); From 3c6ebfa5a9055c71502324aa4c4b93c3d5857cc1 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Wed, 3 Feb 2016 14:03:00 +0100 Subject: [PATCH 019/273] Vier - mobile friendly ACL window and form fields --- js/acl.js | 207 +++++++++++++++++--------------- js/main.js | 2 +- view/templates/acl_selector.tpl | 3 +- view/templates/jot-header.tpl | 21 ++-- view/theme/vier/mobile.css | 74 ++++++++++++ 5 files changed, 197 insertions(+), 110 deletions(-) diff --git a/js/acl.js b/js/acl.js index 487ffafc77..8e4e06c83c 100644 --- a/js/acl.js +++ b/js/acl.js @@ -1,49 +1,56 @@ -function ACL(backend_url, preset, automention){ - that = this; +function ACL(backend_url, preset, automention, is_mobile){ - that.url = backend_url; - that.automention = automention; + this.url = backend_url; + this.automention = automention; + this.is_mobile = is_mobile; - that.kp_timer = null; + + this.kp_timer = null; if (preset==undefined) preset = []; - that.allow_cid = (preset[0] || []); - that.allow_gid = (preset[1] || []); - that.deny_cid = (preset[2] || []); - that.deny_gid = (preset[3] || []); - that.group_uids = []; - that.nw = 4; //items per row. should be calulated from #acl-list.width - - that.list_content = $("#acl-list-content"); - that.item_tpl = unescape($(".acl-list-item[rel=acl-template]").html()); - that.showall = $("#acl-showall"); + this.allow_cid = (preset[0] || []); + this.allow_gid = (preset[1] || []); + this.deny_cid = (preset[2] || []); + this.deny_gid = (preset[3] || []); + this.group_uids = []; - if (preset.length==0) that.showall.addClass("selected"); + if (this.is_mobile) { + this.nw = 1; + } else { + this.nw = 4; + } + + + this.list_content = $("#acl-list-content"); + this.item_tpl = unescape($(".acl-list-item[rel=acl-template]").html()); + this.showall = $("#acl-showall"); + + if (preset.length==0) this.showall.addClass("selected"); /*events*/ - that.showall.click(that.on_showall); - $(document).on("click", ".acl-button-show", that.on_button_show); - $(document).on("click", ".acl-button-hide", that.on_button_hide); - $("#acl-search").keypress(that.on_search); - $("#acl-wrapper").parents("form").submit(that.on_submit); + this.showall.click(this.on_showall); + $(document).on("click", ".acl-button-show", this.on_button_show.bind(this)); + $(document).on("click", ".acl-button-hide", this.on_button_hide.bind(this)); + $("#acl-search").keypress(this.on_search.bind(this)); + $("#acl-wrapper").parents("form").submit(this.on_submit.bind(this)); /* add/remove mentions */ - that.element = $("#profile-jot-text"); - that.htmlelm = that.element.get()[0]; + this.element = $("#profile-jot-text"); + this.htmlelm = this.element.get()[0]; /* startup! */ - that.get(0,100); + this.get(0,100); } ACL.prototype.remove_mention = function(id) { - if (!that.automention) return; - var nick = that.data[id].nick; + if (!this.automention) return; + var nick = this.data[id].nick; var searchText = "@"+nick+"+"+id+" "; if (tinyMCE.activeEditor===null) { - start = that.element.val().indexOf(searchText); + start = this.element.val().indexOf(searchText); if ( start<0) return; end = start+searchText.length; - that.element.setSelection(start,end).replaceSelectedText('').collapseSelection(false); + this.element.setSelection(start,end).replaceSelectedText('').collapseSelection(false); } else { start = tinyMCE.activeEditor.getContent({format : 'raw'}).search( searchText ); if ( start<0 ) return; @@ -54,12 +61,12 @@ ACL.prototype.remove_mention = function(id) { } ACL.prototype.add_mention = function(id) { - if (!that.automention) return; - var nick = that.data[id].nick; + if (!this.automention) return; + var nick = this.data[id].nick; var searchText = "@"+nick+"+"+id+" "; if (tinyMCE.activeEditor===null) { - if ( that.element.val().indexOf( searchText) >= 0 ) return; - that.element.val( searchText + that.element.val() ); + if ( this.element.val().indexOf( searchText) >= 0 ) return; + this.element.val( searchText + this.element.val() ); } else { if ( tinyMCE.activeEditor.getContent({format : 'raw'}).search(searchText) >= 0 ) return; tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'dummy', {}, searchText); @@ -68,46 +75,46 @@ ACL.prototype.add_mention = function(id) { ACL.prototype.on_submit = function(){ aclfileds = $("#acl-fields").html(""); - $(that.allow_gid).each(function(i,v){ + $(this.allow_gid).each(function(i,v){ aclfileds.append("<input type='hidden' name='group_allow[]' value='"+v+"'>"); }); - $(that.allow_cid).each(function(i,v){ + $(this.allow_cid).each(function(i,v){ aclfileds.append("<input type='hidden' name='contact_allow[]' value='"+v+"'>"); }); - $(that.deny_gid).each(function(i,v){ + $(this.deny_gid).each(function(i,v){ aclfileds.append("<input type='hidden' name='group_deny[]' value='"+v+"'>"); }); - $(that.deny_cid).each(function(i,v){ + $(this.deny_cid).each(function(i,v){ aclfileds.append("<input type='hidden' name='contact_deny[]' value='"+v+"'>"); }); } ACL.prototype.search = function(){ var srcstr = $("#acl-search").val(); - that.list_content.html(""); - that.get(0,100, srcstr); + this.list_content.html(""); + this.get(0,100, srcstr); } ACL.prototype.on_search = function(event){ - if (that.kp_timer) clearTimeout(that.kp_timer); - that.kp_timer = setTimeout( that.search, 1000); + if (this.kp_timer) clearTimeout(this.kp_timer); + this.kp_timer = setTimeout( this.search.bind(this), 1000); } ACL.prototype.on_showall = function(event){ event.preventDefault() event.stopPropagation(); - if (that.showall.hasClass("selected")){ + if (this.showall.hasClass("selected")){ return false; } - that.showall.addClass("selected"); + this.showall.addClass("selected"); - that.allow_cid = []; - that.allow_gid = []; - that.deny_cid = []; - that.deny_gid = []; + this.allow_cid = []; + this.allow_gid = []; + this.deny_cid = []; + this.deny_gid = []; - that.update_view(); + this.update_view(); return false; } @@ -117,11 +124,11 @@ ACL.prototype.on_button_show = function(event){ event.stopImmediatePropagation() event.stopPropagation(); - /*that.showall.removeClass("selected"); + /*this.showall.removeClass("selected"); $(this).siblings(".acl-button-hide").removeClass("selected"); $(this).toggleClass("selected");*/ - that.set_allow($(this).parent().attr('id')); + this.set_allow($(this).parent().attr('id')); return false; } @@ -130,11 +137,11 @@ ACL.prototype.on_button_hide = function(event){ event.stopImmediatePropagation() event.stopPropagation(); - /*that.showall.removeClass("selected"); + /*this.showall.removeClass("selected"); $(this).siblings(".acl-button-show").removeClass("selected"); $(this).toggleClass("selected");*/ - that.set_deny($(this).parent().attr('id')); + this.set_deny($(this).parent().attr('id')); return false; } @@ -145,25 +152,25 @@ ACL.prototype.set_allow = function(itemid){ switch(type){ case "g": - if (that.allow_gid.indexOf(id)<0){ - that.allow_gid.push(id) + if (this.allow_gid.indexOf(id)<0){ + this.allow_gid.push(id) }else { - that.allow_gid.remove(id); + this.allow_gid.remove(id); } - if (that.deny_gid.indexOf(id)>=0) that.deny_gid.remove(id); + if (this.deny_gid.indexOf(id)>=0) this.deny_gid.remove(id); break; case "c": - if (that.allow_cid.indexOf(id)<0){ - that.allow_cid.push(id) - if (that.data[id].forum=="1") that.add_mention(id); + if (this.allow_cid.indexOf(id)<0){ + this.allow_cid.push(id) + if (this.data[id].forum=="1") this.add_mention(id); } else { - that.allow_cid.remove(id); - if (that.data[id].forum=="1") that.remove_mention(id); + this.allow_cid.remove(id); + if (this.data[id].forum=="1") this.remove_mention(id); } - if (that.deny_cid.indexOf(id)>=0) that.deny_cid.remove(id); + if (this.deny_cid.indexOf(id)>=0) this.deny_cid.remove(id); break; } - that.update_view(); + this.update_view(); } ACL.prototype.set_deny = function(itemid){ @@ -172,34 +179,34 @@ ACL.prototype.set_deny = function(itemid){ switch(type){ case "g": - if (that.deny_gid.indexOf(id)<0){ - that.deny_gid.push(id) + if (this.deny_gid.indexOf(id)<0){ + this.deny_gid.push(id) } else { - that.deny_gid.remove(id); + this.deny_gid.remove(id); } - if (that.allow_gid.indexOf(id)>=0) that.allow_gid.remove(id); + if (this.allow_gid.indexOf(id)>=0) this.allow_gid.remove(id); break; case "c": - if (that.data[id].forum=="1") that.remove_mention(id); - if (that.deny_cid.indexOf(id)<0){ - that.deny_cid.push(id) + if (this.data[id].forum=="1") this.remove_mention(id); + if (this.deny_cid.indexOf(id)<0){ + this.deny_cid.push(id) } else { - that.deny_cid.remove(id); + this.deny_cid.remove(id); } - if (that.allow_cid.indexOf(id)>=0) that.allow_cid.remove(id); + if (this.allow_cid.indexOf(id)>=0) this.allow_cid.remove(id); break; } - that.update_view(); + this.update_view(); } ACL.prototype.is_show_all = function() { - return (that.allow_gid.length==0 && that.allow_cid.length==0 && - that.deny_gid.length==0 && that.deny_cid.length==0); + return (this.allow_gid.length==0 && this.allow_cid.length==0 && + this.deny_gid.length==0 && this.deny_cid.length==0); } ACL.prototype.update_view = function(){ if (this.is_show_all()){ - that.showall.addClass("selected"); + this.showall.addClass("selected"); /* jot acl */ $('#jot-perms-icon').removeClass('lock').addClass('unlock'); $('#jot-public').show(); @@ -209,7 +216,7 @@ ACL.prototype.update_view = function(){ } } else { - that.showall.removeClass("selected"); + this.showall.removeClass("selected"); /* jot acl */ $('#jot-perms-icon').removeClass('unlock').addClass('lock'); $('#jot-public').hide(); @@ -220,29 +227,29 @@ ACL.prototype.update_view = function(){ $(this).removeClass("groupshow grouphide"); }); - $("#acl-list-content .acl-list-item").each(function(){ - itemid = $(this).attr('id'); + $("#acl-list-content .acl-list-item").each(function(index, element){ + itemid = $(element).attr('id'); type = itemid[0]; id = parseInt(itemid.substr(1)); - btshow = $(this).children(".acl-button-show").removeClass("selected"); - bthide = $(this).children(".acl-button-hide").removeClass("selected"); + btshow = $(element).children(".acl-button-show").removeClass("selected"); + bthide = $(element).children(".acl-button-hide").removeClass("selected"); switch(type){ case "g": var uclass = ""; - if (that.allow_gid.indexOf(id)>=0){ + if (this.allow_gid.indexOf(id)>=0){ btshow.addClass("selected"); bthide.removeClass("selected"); uclass="groupshow"; } - if (that.deny_gid.indexOf(id)>=0){ + if (this.deny_gid.indexOf(id)>=0){ btshow.removeClass("selected"); bthide.addClass("selected"); uclass="grouphide"; } - $(that.group_uids[id]).each(function(i,v) { + $(this.group_uids[id]).each(function(i,v) { if(uclass == "grouphide") $("#c"+v).removeClass("groupshow"); if(uclass != "") { @@ -257,17 +264,17 @@ ACL.prototype.update_view = function(){ break; case "c": - if (that.allow_cid.indexOf(id)>=0){ + if (this.allow_cid.indexOf(id)>=0){ btshow.addClass("selected"); bthide.removeClass("selected"); } - if (that.deny_cid.indexOf(id)>=0){ + if (this.deny_cid.indexOf(id)>=0){ btshow.removeClass("selected"); bthide.addClass("selected"); } } - }); + }.bind(this)); } @@ -281,30 +288,30 @@ ACL.prototype.get = function(start,count, search){ $.ajax({ type:'POST', - url: that.url, + url: this.url, data: postdata, dataType: 'json', - success:that.populate + success:this.populate.bind(this) }); } ACL.prototype.populate = function(data){ - var height = Math.ceil(data.tot / that.nw) * 42; - that.list_content.height(height); - that.data = {}; - $(data.items).each(function(){ - html = "<div class='acl-list-item {4} {5} type{2}' title='{6}' id='{2}{3}'>"+that.item_tpl+"</div>"; - html = html.format(this.photo, this.name, this.type, this.id, (this.forum=='1'?'forum':''), this.network, this.link); - if (this.uids!=undefined) that.group_uids[this.id] = this.uids; + var height = Math.ceil(data.tot / this.nw) * 42; + this.list_content.height(height); + this.data = {}; + $(data.items).each(function(index, item){ + html = "<div class='acl-list-item {4} {5} type{2}' title='{6}' id='{2}{3}'>"+this.item_tpl+"</div>"; + html = html.format(item.photo, item.name, item.type, item.id, (item.forum=='1'?'forum':''), item.network, item.link); + if (item.uids!=undefined) this.group_uids[item.id] = item.uids; //console.log(html); - that.list_content.append(html); - that.data[this.id] = this; - }); - $(".acl-list-item img[data-src]", that.list_content).each(function(i, el){ + this.list_content.append(html); + this.data[item.id] = item; + }.bind(this)); + $(".acl-list-item img[data-src]", this.list_content).each(function(i, el){ // Add src attribute for images with a data-src attribute $(el).attr('src', $(el).data("src")); }); - that.update_view(); + this.update_view(); } diff --git a/js/main.js b/js/main.js index bba5e92969..5a1affe3cb 100644 --- a/js/main.js +++ b/js/main.js @@ -9,7 +9,7 @@ if (h==ch) { return; } - console.log("_resizeIframe", obj, desth, ch); + //console.log("_resizeIframe", obj, desth, ch); if (desth!=ch) { setTimeout(_resizeIframe, 500, obj, ch); } else { diff --git a/view/templates/acl_selector.tpl b/view/templates/acl_selector.tpl index bf9470b64e..bb76135067 100644 --- a/view/templates/acl_selector.tpl +++ b/view/templates/acl_selector.tpl @@ -29,7 +29,8 @@ $(document).ready(function() { acl = new ACL( baseurl+"/acl", [ {{$allowcid}},{{$allowgid}},{{$denycid}},{{$denygid}} ], - {{$features.aclautomention}} + {{$features.aclautomention}}, + {{$APP->is_mobile}} ); } }); diff --git a/view/templates/jot-header.tpl b/view/templates/jot-header.tpl index 647f261c45..b06f6032c3 100644 --- a/view/templates/jot-header.tpl +++ b/view/templates/jot-header.tpl @@ -8,16 +8,24 @@ var plaintext = '{{$editselect}}'; function initEditor(cb){ if (editor==false){ + var colorbox_options = { + {{if $APP->is_mobile}} + 'width' : '100%', + 'height' : '100%', + {{/if}} + 'inline' : true, + 'transition' : 'elastic' + } + + + $("#profile-jot-text-loading").show(); if(plaintext == 'none') { $("#profile-jot-text-loading").hide(); $("#profile-jot-text").css({ 'height': 200, 'color': '#000' }); $("#profile-jot-text").contact_autocomplete(baseurl+"/acl"); editor = true; - $("a#jot-perms-icon").colorbox({ - 'inline' : true, - 'transition' : 'elastic' - }); + $("a#jot-perms-icon").colorbox(colorbox_options); $(".jothidden").show(); if (typeof cb!="undefined") cb(); return; @@ -107,10 +115,7 @@ function initEditor(cb){ }); editor = true; // setup acl popup - $("a#jot-perms-icon").colorbox({ - 'inline' : true, - 'transition' : 'elastic' - }); + $("a#jot-perms-icon").colorbox(colorbox_options); } else { if (typeof cb!="undefined") cb(); } diff --git a/view/theme/vier/mobile.css b/view/theme/vier/mobile.css index f6ec2b8ccb..128fb469c6 100644 --- a/view/theme/vier/mobile.css +++ b/view/theme/vier/mobile.css @@ -161,3 +161,77 @@ aside.show { } .tabs.show::after { display: none; } .tabs.show .tab { display: block; } + +/* ACL window */ +#profile-jot-acl-wrapper, #profile-jot-acl-wrapper * { box-sizing: border-box; } +#acl-wrapper { width: 100%; float: none; } +#acl-search { width: 100%; float: none; padding-right: 0px; margin-bottom: 1em; } +#acl-showall { width: 100%; height: 48px; margin-bottom: 1em; } +.acl-list-item { width: auto; float: none; height: auto; overflow: hidden; position: relative;} +.acl-list-item img { width: 48px; height: 48px; } +.acl-list-item p { height: auto; font-size: inherit; } +.acl-list-item a { + float: none; + position: absolute; + top: 5px; + right: 5px; + height: 48px; + padding: 10px 2px 2px 2px; + font-size: 12px; + width: 20%; + text-align: center; + background-position: center 5px; +} +.acl-list-item a.acl-button-hide { right: 25%; } +/* flexbox for ACL window */ +#cboxLoadedContent, +#cboxLoadedContent > div, +#acl-wrapper { + display: -ms-Flexbox !important; + -ms-box-orient: vertival; + + display: -webkit-flex !important; + display: -moz-flex !important; + display: -ms-flex !important; + display: flex !important; + + -webkit-flex-flow: column; + -moz-flex-flow: column; + -ms-flex-flow: column; + flex-flow: column; + + -webkit-flex: 1 100%; + -moz-flex: 1 100%; + -ms-flex: 1 100%; + flex: 1 100%; +} +#acl-list { + -webkit-flex: 1 1 auto; + -moz-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; +} + +/** input elements **/ +input, +textarea, +select { + font-size: 18px; + border: 1px solid #888; + padding: 0.2em; +} +input:focus, +textarea:focus, +select:focus { + box-shadow: 1px 1px 10px rgba(46, 151, 255, 0.62); +} + +.field, .field > * { box-sizing: border-box; } +.field label { width: 100%; float: none; display: block; } +.field input, .field textarea, .field select { max-width: 100%; width: 100%; } +.field.yesno .onoff, +.field.checkbox input { width: auto; float: right; } +.field.yesno label, +.field.checkbox label { width: 70%; float: left; } +.field .field_help { margin: 0; } + From 1ddae21cff0a75e4801eac146435069edf96ae51 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Wed, 3 Feb 2016 14:48:46 +0100 Subject: [PATCH 020/273] Vier mobile: fix content width, events style, oembed --- view/theme/vier/mobile.css | 40 +++++++++++++++++++++++++++++++++----- view/theme/vier/style.css | 4 +++- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/view/theme/vier/mobile.css b/view/theme/vier/mobile.css index 128fb469c6..edba28e5af 100644 --- a/view/theme/vier/mobile.css +++ b/view/theme/vier/mobile.css @@ -8,11 +8,11 @@ aside, header, #nav-events-link, #search-box, #nav-admin-link, #activitiy-by-dat } section { - /* left: calc((100% - (784px)) / 2); */ + box-sizing: border-box; left: 0px; - width: calc(100% - 20px); + width: 100%; max-width: 100%; - padding: 10px; + padding: 5px; } body, section, nav .nav-menu, div.pager, ul.tabs { @@ -92,8 +92,8 @@ nav ul { .wall-item-container.thread_level_5, .wall-item-container.thread_level_6, .wall-item-container.thread_level_7 { - margin-left: 60px; - width: calc(100% - 70px); + margin-left: 0; + width: calc(100% - 10px); } .wall-item-container.thread_level_2 .wall-item-content, @@ -105,6 +105,10 @@ nav ul { max-width: 100%; } +.wall-item-container .wall-item-info { width: auto; } +.contact-photo-wrapper { width: 55px; } +.wwto { left: 0px; } + /* aside in/out */ .mobile-aside-toggle { display: block !important; @@ -235,3 +239,29 @@ select:focus { .field.checkbox label { width: 70%; float: left; } .field .field_help { margin: 0; } +/** event **/ +.event-start, .event-end { width: auto; } +.event-start .dtstart, .event-end .dtend { float: none; display: block; } + +/** oembed **/ +.embed_video { + float: none; + margin: 0px; + height: 120px; + width: 100%; + overflow: hidden; + display: block; +} +.embed_video > img { + display: block; + width: 100% !important; + height: auto !important; + max-width: 100% !important; +} +.embed_video > div { + background-image: none !important; background-color: transparent !important; + width: 100% !important; height: 110px !important; +} + +.type-link > a { height: 120px; width: 100%; overflow: hidden; display: block; } +.type-link > a > img { display: block; max-width: 100%!important; width: 100% !important; height: auto !important; } \ No newline at end of file diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index e0abee837c..cdbda71fda 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -2748,7 +2748,9 @@ a.mail-list-link { margin-bottom: 15px; } -.vevent .event-description, .vevent .event-location { +.vevent .event-summary, +.vevent .event-description, +.vevent .event-location { margin-left: 10px; margin-right: 10px; } From f21e3e46c7f7258ba237dfa5002b9b07624a5c57 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Wed, 3 Feb 2016 15:01:24 +0100 Subject: [PATCH 021/273] Vier mobile: better jot buttons on small screens --- view/theme/vier/mobile.css | 13 +++++++++++++ view/theme/vier/style.css | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/view/theme/vier/mobile.css b/view/theme/vier/mobile.css index edba28e5af..b67dc5f771 100644 --- a/view/theme/vier/mobile.css +++ b/view/theme/vier/mobile.css @@ -166,6 +166,19 @@ aside.show { .tabs.show::after { display: none; } .tabs.show .tab { display: block; } +/* jot buttons */ +#profile-jot-submit, +#jot-preview-link { + float: none; + display: block; + width: 100%; + margin: 0 0 1em 0; +} +#profile-jot-submit-wrapper > div { + margin: 0 1em 1em 0; +} +#profile-jot-submit-wrapper > div#profile-jot-perms { margin: 0; } + /* ACL window */ #profile-jot-acl-wrapper, #profile-jot-acl-wrapper * { box-sizing: border-box; } #acl-wrapper { width: 100%; float: none; } diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index cdbda71fda..7d7e2683a4 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -127,8 +127,8 @@ img { .icon { background-color: transparent ; background-repeat: no-repeat; - width: 18px; - height: 18px; + width: 20px; + height: 20px; /* display: block; */ display: inline-block; overflow: hidden; From c1070e4655996e047951dada992b65abebf74834 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Wed, 3 Feb 2016 18:05:26 +0100 Subject: [PATCH 022/273] forums.php is now forum.php and providing class forum --- include/forum.php | 190 ++++++++++++++++++++++++++++++++++++++ include/forums.php | 185 ------------------------------------- include/identity.php | 4 +- mod/network.php | 4 +- mod/ping.php | 4 +- view/theme/vier/theme.php | 4 +- 6 files changed, 198 insertions(+), 193 deletions(-) create mode 100644 include/forum.php delete mode 100644 include/forums.php diff --git a/include/forum.php b/include/forum.php new file mode 100644 index 0000000000..290f0134a1 --- /dev/null +++ b/include/forum.php @@ -0,0 +1,190 @@ +<?php + +/** + * @file include/forum.php + * @brief Functions related to forum functionality * + */ + +/** + * @brief This class handles functions related to the forum functionality + */ +class forum { + + /** + * @brief Function to list all forums a user is connected with + * + * @param int $uid of the profile owner + * @param boolean $showhidden + * Show frorums which are not hidden + * @param boolean $lastitem + * Sort by lastitem + * @param boolean $showprivate + * Show private groups + * + * @returns array + * 'url' => forum url + * 'name' => forum name + * 'id' => number of the key from the array + * 'micro' => contact photo in format micro + */ + public static function get_forumlist($uid, $showhidden = true, $lastitem, $showprivate = false) { + + $forumlist = array(); + + $order = (($showhidden) ? '' : ' AND NOT `hidden` '); + $order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC '); + $select = '`forum` '; + if ($showprivate) { + $select = '(`forum` OR `prv`)'; + } + + $contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact` + WHERE `network`= 'dfrn' AND $select AND `uid` = %d + AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive` + AND `success_update` > `failure_update` + $order ", + intval($uid) + ); + + if (!$contacts) + return($forumlist); + + foreach($contacts as $contact) { + $forumlist[] = array( + 'url' => $contact['url'], + 'name' => $contact['name'], + 'id' => $contact['id'], + 'micro' => $contact['micro'], + ); + } + return($forumlist); + } + + + /** + * @brief Forumlist widget + * + * Sidebar widget to show subcribed friendica forums. If activated + * in the settings, it appears at the notwork page sidebar + * + * @param int $uid The ID of the User + * @param int $cid + * The contact id which is used to mark a forum as "selected" + * @return string + */ + public static function widget_forumlist($uid,$cid = 0) { + + if(! intval(feature_enabled(local_user(),'forumlist_widget'))) + return; + + $o = ''; + + //sort by last updated item + $lastitem = true; + + $contacts = self::get_forumlist($uid,true,$lastitem, true); + $total = count($contacts); + $visible_forums = 10; + + if(count($contacts)) { + + $id = 0; + + foreach($contacts as $contact) { + + $selected = (($cid == $contact['id']) ? ' forum-selected' : ''); + + $entry = array( + 'url' => z_root() . '/network?f=&cid=' . $contact['id'], + 'external_url' => z_root() . '/redir/' . $contact['id'], + 'name' => $contact['name'], + 'cid' => $contact['id'], + 'selected' => $selected, + 'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO), + 'id' => ++$id, + ); + $entries[] = $entry; + } + + $tpl = get_markup_template('widget_forumlist.tpl'); + + $o .= replace_macros($tpl,array( + '$title' => t('Forums'), + '$forums' => $entries, + '$link_desc' => t('External link to forum'), + '$total' => $total, + '$visible_forums' => $visible_forums, + '$showmore' => t('show more'), + )); + } + + return $o; + } + + /** + * @brief Format forumlist as contact block + * + * This function is used to show the forumlist in + * the advanced profile. + * + * @param int $uid The ID of the User + * @return string + * + */ + public static function forumlist_profile_advanced($uid) { + + $profile = intval(feature_enabled($uid,'forumlist_profile')); + if(! $profile) + return; + + $o = ''; + + // place holder in case somebody wants configurability + $show_total = 9999; + + //don't sort by last updated item + $lastitem = false; + + $contacts = self::get_forumlist($uid,false,$lastitem,false); + + $total_shown = 0; + + foreach($contacts as $contact) { + $forumlist .= micropro($contact,false,'forumlist-profile-advanced'); + $total_shown ++; + if($total_shown == $show_total) + break; + } + + if(count($contacts) > 0) + $o .= $forumlist; + return $o; + } + + /** + * @brief count unread forum items + * + * Count unread items of connected forums and private groups + * + * @return array + * 'id' => contact id + * 'name' => contact/forum name + * 'count' => counted unseen forum items + * + */ + public static function count_unseen_items() { + $r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item` + INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id` + WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen` + AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`) + AND NOT `contact`.`blocked` AND NOT `contact`.`hidden` + AND NOT `contact`.`pending` AND NOT `contact`.`archive` + AND `contact`.`success_update` > `failure_update` + GROUP BY `contact`.`id` ", + intval(local_user()) + ); + + return $r; + } + +} \ No newline at end of file diff --git a/include/forums.php b/include/forums.php deleted file mode 100644 index 952d09a494..0000000000 --- a/include/forums.php +++ /dev/null @@ -1,185 +0,0 @@ -<?php - -/** - * @file include/forums.php - * @brief Functions related to forum functionality * - */ - - -/** - * @brief Function to list all forums a user is connected with - * - * @param int $uid of the profile owner - * @param boolean $showhidden - * Show frorums which are not hidden - * @param boolean $lastitem - * Sort by lastitem - * @param boolean $showprivate - * Show private groups - * - * @returns array - * 'url' => forum url - * 'name' => forum name - * 'id' => number of the key from the array - * 'micro' => contact photo in format micro - */ -function get_forumlist($uid, $showhidden = true, $lastitem, $showprivate = false) { - - $forumlist = array(); - - $order = (($showhidden) ? '' : ' AND NOT `hidden` '); - $order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC '); - $select = '`forum` '; - if ($showprivate) { - $select = '(`forum` OR `prv`)'; - } - - $contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact` - WHERE `network`= 'dfrn' AND $select AND `uid` = %d - AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive` - AND `success_update` > `failure_update` - $order ", - intval($uid) - ); - - if (!$contacts) - return($forumlist); - - foreach($contacts as $contact) { - $forumlist[] = array( - 'url' => $contact['url'], - 'name' => $contact['name'], - 'id' => $contact['id'], - 'micro' => $contact['micro'], - ); - } - return($forumlist); -} - - -/** - * @brief Forumlist widget - * - * Sidebar widget to show subcribed friendica forums. If activated - * in the settings, it appears at the notwork page sidebar - * - * @param int $uid - * @param int $cid - * The contact id which is used to mark a forum as "selected" - * @return string - */ -function widget_forumlist($uid,$cid = 0) { - - if(! intval(feature_enabled(local_user(),'forumlist_widget'))) - return; - - $o = ''; - - //sort by last updated item - $lastitem = true; - - $contacts = get_forumlist($uid,true,$lastitem, true); - $total = count($contacts); - $visible_forums = 10; - - if(count($contacts)) { - - $id = 0; - - foreach($contacts as $contact) { - - $selected = (($cid == $contact['id']) ? ' forum-selected' : ''); - - $entry = array( - 'url' => z_root() . '/network?f=&cid=' . $contact['id'], - 'external_url' => z_root() . '/redir/' . $contact['id'], - 'name' => $contact['name'], - 'cid' => $contact['id'], - 'selected' => $selected, - 'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO), - 'id' => ++$id, - ); - $entries[] = $entry; - } - - $tpl = get_markup_template('widget_forumlist.tpl'); - - $o .= replace_macros($tpl,array( - '$title' => t('Forums'), - '$forums' => $entries, - '$link_desc' => t('External link to forum'), - '$total' => $total, - '$visible_forums' => $visible_forums, - '$showmore' => t('show more'), - )); - } - - return $o; -} - -/** - * @brief Format forumlist as contact block - * - * This function is used to show the forumlist in - * the advanced profile. - * - * @param int $uid - * @return string - * - */ -function forumlist_profile_advanced($uid) { - - $profile = intval(feature_enabled($uid,'forumlist_profile')); - if(! $profile) - return; - - $o = ''; - - // place holder in case somebody wants configurability - $show_total = 9999; - - //don't sort by last updated item - $lastitem = false; - - $contacts = get_forumlist($uid,false,$lastitem,false); - - $total_shown = 0; - - foreach($contacts as $contact) { - $forumlist .= micropro($contact,false,'forumlist-profile-advanced'); - $total_shown ++; - if($total_shown == $show_total) - break; - } - - if(count($contacts) > 0) - $o .= $forumlist; - return $o; -} - -/** - * @brief count unread forum items - * - * Count unread items of connected forums and private groups - * - * @return array - * 'id' => contact id - * 'name' => contact/forum name - * 'count' => counted unseen forum items - * - */ - -function forums_count_unseen() { - $r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item` - INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id` - WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen` - AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`) - AND NOT `contact`.`blocked` AND NOT `contact`.`hidden` - AND NOT `contact`.`pending` AND NOT `contact`.`archive` - AND `contact`.`success_update` > `failure_update` - GROUP BY `contact`.`id` ", - intval(local_user()) - ); - - return $r; -} diff --git a/include/identity.php b/include/identity.php index fdd28f81ce..87c91e6dcc 100644 --- a/include/identity.php +++ b/include/identity.php @@ -3,7 +3,7 @@ * @file include/identity.php */ -require_once('include/forums.php'); +require_once('include/forum.php'); require_once('include/bbcode.php'); require_once("mod/proxy.php"); @@ -655,7 +655,7 @@ function advanced_profile(&$a) { //show subcribed forum if it is enabled in the usersettings if (feature_enabled($uid,'forumlist_profile')) { - $profile['forumlist'] = array( t('Forums:'), forumlist_profile_advanced($uid)); + $profile['forumlist'] = array( t('Forums:'), forum::forumlist_profile_advanced($uid)); } if ($a->profile['uid'] == local_user()) diff --git a/mod/network.php b/mod/network.php index a07c5868ec..151384cd67 100644 --- a/mod/network.php +++ b/mod/network.php @@ -114,7 +114,7 @@ function network_init(&$a) { require_once('include/group.php'); require_once('include/contact_widgets.php'); require_once('include/items.php'); - require_once('include/forums.php'); + require_once('include/forum.php'); if(! x($a->page,'aside')) $a->page['aside'] = ''; @@ -148,7 +148,7 @@ function network_init(&$a) { } $a->page['aside'] .= (feature_enabled(local_user(),'groups') ? group_side('network/0','network','standard',$group_id) : ''); - $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? widget_forumlist(local_user(),$cid) : ''); + $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? forum::widget_forumlist(local_user(),$cid) : ''); $a->page['aside'] .= posted_date_widget($a->get_baseurl() . '/network',local_user(),false); $a->page['aside'] .= networks_widget($a->get_baseurl(true) . '/network',(x($_GET, 'nets') ? $_GET['nets'] : '')); $a->page['aside'] .= saved_searches($search); diff --git a/mod/ping.php b/mod/ping.php index 57728d3294..3d34e2fe62 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -1,7 +1,7 @@ <?php require_once("include/datetime.php"); require_once('include/bbcode.php'); -require_once('include/forums.php'); +require_once('include/forum.php'); require_once('include/group.php'); require_once("mod/proxy.php"); @@ -96,7 +96,7 @@ function ping_init(&$a) { } if(intval(feature_enabled(local_user(),'forumlist_widget'))) { - $forums_unseen = forums_count_unseen(); + $forums_unseen = forum::count_unseen_items(); } } diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php index 4afc5409c0..93a6d5ce08 100644 --- a/view/theme/vier/theme.php +++ b/view/theme/vier/theme.php @@ -220,7 +220,7 @@ function vier_community_info() { //Community_Pages at right_aside if($show_pages AND local_user()) { - require_once('include/forums.php'); + require_once('include/forum.php'); if(x($_GET['cid']) && intval($_GET['cid']) != 0) $cid = $_GET['cid']; @@ -228,7 +228,7 @@ function vier_community_info() { //sort by last updated item $lastitem = true; - $contacts = get_forumlist($a->user['uid'],true,$lastitem, true); + $contacts = forum::get_forumlist($a->user['uid'],true,$lastitem, true); $total = count($contacts); $visible_forums = 10; From 811ed4e1c0bad71c29af927267dbc85b57dc6027 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Wed, 3 Feb 2016 19:48:09 +0100 Subject: [PATCH 023/273] datetime.php cleanup --- include/datetime.php | 407 ++++++++++++++++++++++++++----------------- 1 file changed, 247 insertions(+), 160 deletions(-) diff --git a/include/datetime.php b/include/datetime.php index a05af5e38f..3a75690d22 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -1,8 +1,17 @@ <?php +/** + * @file include/datetime.php + * @brief Some functions for date and time related tasks. + */ -// two-level sort for timezones. -if(! function_exists('timezone_cmp')) { +/** + * @brief Two-level sort for timezones. + * + * @param string $a + * @param string $b + * @return int + */ function timezone_cmp($a, $b) { if(strstr($a,'/') && strstr($b,'/')) { if ( t($a) == t($b)) return 0; @@ -11,11 +20,16 @@ function timezone_cmp($a, $b) { if(strstr($a,'/')) return -1; if(strstr($b,'/')) return 1; if ( t($a) == t($b)) return 0; - return ( t($a) < t($b)) ? -1 : 1; -}} -// emit a timezone selector grouped (primarily) by continent -if(! function_exists('select_timezone')) { + return ( t($a) < t($b)) ? -1 : 1; +} + +/** + * @brief Emit a timezone selector grouped (primarily) by continent + * + * @param string $current Timezone + * @return string Parsed HTML output + */ function select_timezone($current = 'America/Los_Angeles') { $timezone_identifiers = DateTimeZone::listIdentifiers(); @@ -52,13 +66,25 @@ function select_timezone($current = 'America/Los_Angeles') { } $o .= '</optgroup></select>'; return $o; -}} +} -// return a select using 'field_select_raw' template, with timezones -// groupped (primarily) by continent -// arguments follow convetion as other field_* template array: -// 'name', 'label', $value, 'help' -if (!function_exists('field_timezone')){ + + +/** + * @brief Generating a Timezone selector + * + * Return a select using 'field_select_raw' template, with timezones + * groupped (primarily) by continent +.* arguments follow convetion as other field_* template array: +.* 'name', 'label', $value, 'help' + * + * @param string $name Name of the selector + * @param string $label Label for the selector + * @param string $current Timezone + * @param string $help Help text + * + * @return string Parsed HTML + */ function field_timezone($name='timezone', $label='', $current = 'America/Los_Angeles', $help){ $options = select_timezone($current); $options = str_replace('<select id="timezone_select" name="timezone">','', $options); @@ -69,15 +95,19 @@ function field_timezone($name='timezone', $label='', $current = 'America/Los_Ang '$field' => array($name, $label, $current, $help, $options), )); -}} +} -// General purpose date parse/convert function. -// $from = source timezone -// $to = dest timezone -// $s = some parseable date/time string -// $fmt = output format - -if(! function_exists('datetime_convert')) { +/** + * @brief General purpose date parse/convert function. + * + * @param string $from Source timezone + * @param string $to Dest timezone + * @param string $s Some parseable date/time string + * @param string $fmt Output format recognised from php's DateTime class + * http://www.php.net/manual/en/datetime.format.php + * + * @return string + */ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d H:i:s") { // Defaults to UTC if nothing is set, but throws an exception if set to empty string. @@ -124,13 +154,18 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d $d->setTimeZone($to_obj); return($d->format($fmt)); -}} +} -// wrapper for date selector, tailored for use in birthday fields - +/** + * @brief Wrapper for date selector, tailored for use in birthday fields. + * + * @param string $dob Date of Birth + * @return string + */ function dob($dob) { list($year,$month,$day) = sscanf($dob,'%4d-%2d-%2d'); + $f = get_config('system','birthday_input_format'); if(! $f) $f = 'ymd'; @@ -138,62 +173,69 @@ function dob($dob) { $value = ''; else $value = (($year) ? datetime_convert('UTC','UTC',$dob,'Y-m-d') : datetime_convert('UTC','UTC',$dob,'m-d')); + $o = '<input type="text" name="dob" value="' . $value . '" placeholder="' . t('YYYY-MM-DD or MM-DD') . '" />'; + // if ($dob && $dob != '0000-00-00') // $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),mktime(0,0,0,$month,$day,$year),'dob'); // else // $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),false,'dob'); + return $o; } /** - * returns a date selector - * @param $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param $min - * unix timestamp of minimum date - * @param $max - * unix timestap of maximum date - * @param $default - * unix timestamp of default date - * @param $id - * id and name of datetimepicker (defaults to "datetimepicker") + * @brief Returns a date selector + * + * @param string $format + * Format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param string $min + * Unix timestamp of minimum date + * @param string $max + * Unix timestap of maximum date + * @param string $default + * Unix timestamp of default date + * @param string $id + * ID and name of datetimepicker (defaults to "datetimepicker") + * + * @return string Parsed HTML output. */ -if(! function_exists('datesel')) { function datesel($format, $min, $max, $default, $id = 'datepicker') { return datetimesel($format,$min,$max,$default,$id,true,false, '',''); -}} +} /** - * returns a time selector - * @param $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @brief Returns a time selector + * + * @param string $format + * Format string, e.g. 'ymd' or 'mdy'. Not currently supported * @param $h - * already selected hour + * Already selected hour * @param $m - * already selected minute - * @param $id - * id and name of datetimepicker (defaults to "timepicker") + * Already selected minute + * @param string $id + * ID and name of datetimepicker (defaults to "timepicker") + * + * @return string Parsed HTML output. */ -if(! function_exists('timesel')) { function timesel($format, $h, $m, $id='timepicker') { return datetimesel($format,new DateTime(),new DateTime(),new DateTime("$h:$m"),$id,false,true); -}} +} /** * @brief Returns a datetime selector. * - * @param $format + * @param string $format * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param $min + * @param string $min * unix timestamp of minimum date - * @param $max + * @param string $max * unix timestap of maximum date - * @param $default + * @param string $default * unix timestamp of default date * @param string $id * id and name of datetimepicker (defaults to "datetimepicker") - * @param boolean $pickdate + * @param bool $pickdate * true to show date picker (default) * @param boolean $picktime * true to show time picker (default) @@ -201,17 +243,15 @@ function timesel($format, $h, $m, $id='timepicker') { * set minimum date from picker with id $minfrom (none by default) * @param $maxfrom * set maximum date from picker with id $maxfrom (none by default) - * @param boolean $required default false + * @param bool $required default false + * * @return string Parsed HTML output. * * @todo Once browser support is better this could probably be replaced with * native HTML5 date picker. */ -if(! function_exists('datetimesel')) { function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pickdate = true, $picktime = true, $minfrom = '', $maxfrom = '', $required = false) { - $a = get_app(); - // First day of the week (0 = Sunday) $firstDay = get_pconfig(local_user(),'system','first_day_of_week'); if ($firstDay === false) $firstDay=0; @@ -224,43 +264,58 @@ function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pic $o = ''; $dateformat = ''; + if($pickdate) $dateformat .= 'Y-m-d'; if($pickdate && $picktime) $dateformat .= ' '; if($picktime) $dateformat .= 'H:i'; + $minjs = $min ? ",minDate: new Date({$min->getTimestamp()}*1000), yearStart: " . $min->format('Y') : ''; $maxjs = $max ? ",maxDate: new Date({$max->getTimestamp()}*1000), yearEnd: " . $max->format('Y') : ''; $input_text = $default ? 'value="' . date($dateformat, $default->getTimestamp()) . '"' : ''; $defaultdatejs = $default ? ",defaultDate: new Date({$default->getTimestamp()}*1000)" : ''; + $pickers = ''; if(!$pickdate) $pickers .= ',datepicker: false'; if(!$picktime) $pickers .= ',timepicker: false'; + $extra_js = ''; $pickers .= ",dayOfWeekStart: ".$firstDay.",lang:'".$lang."'"; if($minfrom != '') $extra_js .= "\$('#$minfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({minDate: currentDateTime})}})"; if($maxfrom != '') $extra_js .= "\$('#$maxfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({maxDate: currentDateTime})}})"; + $readable_format = $dateformat; $readable_format = str_replace('Y','yyyy',$readable_format); $readable_format = str_replace('m','mm',$readable_format); $readable_format = str_replace('d','dd',$readable_format); $readable_format = str_replace('H','HH',$readable_format); $readable_format = str_replace('i','MM',$readable_format); + $o .= "<div class='date'><input type='text' placeholder='$readable_format' name='$id' id='$id' $input_text />"; $o .= '</div>'; $o .= "<script type='text/javascript'>"; $o .= "\$(function () {var picker = \$('#$id').datetimepicker({step:5,format:'$dateformat' $minjs $maxjs $pickers $defaultdatejs}); $extra_js})"; $o .= "</script>"; + return $o; -}} +} -// implements "3 seconds ago" etc. -// based on $posted_date, (UTC). -// Results relative to current timezone -// Limited to range of timestamps - -if(! function_exists('relative_date')) { +/** + * @brief Returns a relative date string. + * + * Implements "3 seconds ago" etc. + * Based on $posted_date, (UTC). + * Results relative to current timezone. + * Limited to range of timestamps. + * + * @param string $posted_date + * @param string $format (optional) Parsed with sprintf() + * <tt>%1$d %2$s ago</tt>, e.g. 22 hours ago, 1 minute ago + * + * @return string with relative date + */ function relative_date($posted_date,$format = null) { $localtime = datetime_convert('UTC',date_default_timezone_get(),$posted_date); @@ -300,23 +355,33 @@ function relative_date($posted_date,$format = null) { // translators - e.g. 22 hours ago, 1 minute ago if(! $format) $format = t('%1$d %2$s ago'); + return sprintf( $format,$r, (($r == 1) ? $str[0] : $str[1])); - } - } -}} - - - -// Returns age in years, given a date of birth, -// the timezone of the person whose date of birth is provided, -// and the timezone of the person viewing the result. -// Why? Bear with me. Let's say I live in Mittagong, Australia, and my -// birthday is on New Year's. You live in San Bruno, California. -// When exactly are you going to see my age increase? -// A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start -// celebrating and become a year older. If you wish me happy birthday -// on January 1 (San Bruno time), you'll be a day late. + } + } +} +/** + * @brief Returns timezone correct age in years. + * + * Returns the age in years, given a date of birth, the timezone of the person + * whose date of birth is provided, and the timezone of the person viewing the + * result. + * + * Why? Bear with me. Let's say I live in Mittagong, Australia, and my birthday + * is on New Year's. You live in San Bruno, California. + * When exactly are you going to see my age increase? + * + * A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start celebrating + * and become a year older. If you wish me happy birthday on January 1 + * (San Bruno time), you'll be a day late. + * + * @param string $dob Date of Birth + * @param string $owner_tz (optional) Timezone of the person of interest + * @param string $viewer_tz (optional) Timezone of the person viewing + * + * @return int + */ function age($dob,$owner_tz = '',$viewer_tz = '') { if(! intval($dob)) return 0; @@ -333,64 +398,79 @@ function age($dob,$owner_tz = '',$viewer_tz = '') { if(($curr_month < $month) || (($curr_month == $month) && ($curr_day < $day))) $year_diff--; + return $year_diff; } - - -// Get days in month -// get_dim($year, $month); -// returns number of days. -// $month[1] = 'January'; -// to match human usage. - -if(! function_exists('get_dim')) { +/** + * @brief Get days of a month in a given year. + * + * Returns number of days in the month of the given year. + * $m = 1 is 'January' to match human usage. + * + * @param int $y Year + * @param int $m Month (1=January, 12=December) + * + * @return int Number of days in the given month + */ function get_dim($y,$m) { - $dim = array( 0, - 31, 28, 31, 30, 31, 30, - 31, 31, 30, 31, 30, 31); - - if($m != 2) - return $dim[$m]; - if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0)) - return 29; - return $dim[2]; -}} + $dim = array( 0, + 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31); + if($m != 2) + return $dim[$m]; -// Returns the first day in month for a given month, year -// get_first_dim($year,$month) -// returns 0 = Sunday through 6 = Saturday -// Months start at 1. + if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0)) + return 29; -if(! function_exists('get_first_dim')) { + return $dim[2]; +} + +/** + * @brief Returns the first day in month for a given month, year. + * + * Months start at 1. + * + * @param int $y Year + * @param int $m Month (1=January, 12=December) + * + * @return string day 0 = Sunday through 6 = Saturday + */ function get_first_dim($y,$m) { - $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m)); - return datetime_convert('UTC','UTC',$d,'w'); -}} + $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m)); -// output a calendar for the given month, year. -// if $links are provided (array), e.g. $links[12] => 'http://mylink' , -// date 12 will be linked appropriately. Today's date is also noted by -// altering td class. -// Months count from 1. + return datetime_convert('UTC','UTC',$d,'w'); +} - -/// @TODO Provide (prev,next) links, define class variations for different size calendars - - -if(! function_exists('cal')) { +/** + * @brief Output a calendar for the given month, year. + * + * If $links are provided (array), e.g. $links[12] => 'http://mylink' , + * date 12 will be linked appropriately. Today's date is also noted by + * altering td class. + * Months count from 1. + * + * @param int $y Year + * @param int $m Month + * @param bool $links (default false) + * @param string $class + * + * @return string + * + * @todo Provide (prev,next) links, define class variations for different size calendars + */ function cal($y = 0,$m = 0, $links = false, $class='') { // month table - start at 1 to match human usage. $mtab = array(' ', - 'January','February','March', - 'April','May','June', - 'July','August','September', - 'October','November','December' + 'January','February','March', + 'April','May','June', + 'July','August','September', + 'October','November','December' ); $thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); @@ -400,54 +480,63 @@ function cal($y = 0,$m = 0, $links = false, $class='') { if(! $m) $m = intval($thismonth); - $dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); - $f = get_first_dim($y,$m); - $l = get_dim($y,$m); - $d = 1; - $dow = 0; - $started = false; + $dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); + $f = get_first_dim($y,$m); + $l = get_dim($y,$m); + $d = 1; + $dow = 0; + $started = false; - if(($y == $thisyear) && ($m == $thismonth)) - $tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j')); + if(($y == $thisyear) && ($m == $thismonth)) + $tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j')); $str_month = day_translate($mtab[$m]); - $o = '<table class="calendar' . $class . '">'; - $o .= "<caption>$str_month $y</caption><tr>"; - for($a = 0; $a < 7; $a ++) - $o .= '<th>' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '</th>'; - $o .= '</tr><tr>'; + $o = '<table class="calendar' . $class . '">'; + $o .= "<caption>$str_month $y</caption><tr>"; + for($a = 0; $a < 7; $a ++) + $o .= '<th>' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '</th>'; - while($d <= $l) { - if(($dow == $f) && (! $started)) - $started = true; - $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : ''); - $o .= "<td $today>"; - $day = str_replace(' ',' ',sprintf('%2.2d', $d)); - if($started) { - if(is_array($links) && isset($links[$d])) - $o .= "<a href=\"{$links[$d]}\">$day</a>"; - else - $o .= $day; - $d ++; - } - else - $o .= ' '; - $o .= '</td>'; - $dow ++; - if(($dow == 7) && ($d <= $l)) { - $dow = 0; - $o .= '</tr><tr>'; - } - } - if($dow) - for($a = $dow; $a < 7; $a ++) - $o .= '<td> </td>'; - $o .= '</tr></table>'."\r\n"; + $o .= '</tr><tr>'; - return $o; -}} + while($d <= $l) { + if(($dow == $f) && (! $started)) + $started = true; + $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : ''); + $o .= "<td $today>"; + $day = str_replace(' ',' ',sprintf('%2.2d', $d)); + if($started) { + if(is_array($links) && isset($links[$d])) + $o .= "<a href=\"{$links[$d]}\">$day</a>"; + else + $o .= $day; + $d ++; + } else { + $o .= ' '; + } + + $o .= '</td>'; + $dow ++; + if(($dow == 7) && ($d <= $l)) { + $dow = 0; + $o .= '</tr><tr>'; + } + } + if($dow) + for($a = $dow; $a < 7; $a ++) + $o .= '<td> </td>'; + + $o .= '</tr></table>'."\r\n"; + + return $o; +} + +/** + * @brief Create a birthday event. + * + * Update the year and the birthday. + */ function update_contact_birthdays() { // This only handles foreign or alien networks where a birthday has been provided. @@ -474,8 +563,6 @@ function update_contact_birthdays() { $bdtext = sprintf( t('%s\'s birthday'), $rr['name']); $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $rr['url'] . ']' . $rr['name'] . '[/url]') ; - - $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`,`adjust`) VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d' ) ", intval($rr['uid']), From c7158772619f2b38a55b22c7639ee3fd58bc0db0 Mon Sep 17 00:00:00 2001 From: Silke Meyer <silke@silkemeyer.net> Date: Wed, 3 Feb 2016 20:47:41 +0100 Subject: [PATCH 024/273] Slightly updated docs about Let's Encrypt --- doc/SSL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/SSL.md b/doc/SSL.md index a72eec2a16..bcff929fe5 100644 --- a/doc/SSL.md +++ b/doc/SSL.md @@ -90,8 +90,8 @@ If you run your own server, upload the files and check out the Mozilla wiki link Let's encrypt --- -If you run your own server and you control your name server, the "Let's encrypt" initiative might become an interesting alternative. -Their offer is not ready, yet. +If you run your own server, the "Let's encrypt" initiative might become an interesting alternative. +Their offer is in public beta right now. Check out [their website](https://letsencrypt.org/) for status updates. Web server settings From 2d0c2990ea13a756578b3117efeee788413648b6 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 3 Feb 2016 23:04:52 +0100 Subject: [PATCH 025/273] Differences fixed between old and new code --- include/import-dfrn.php | 80 +++++++++++++++++++++-------------------- include/items.php | 30 ++++++++++++---- 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index bfcb002bb1..981e596432 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -18,12 +18,12 @@ require_once("include/items.php"); require_once("include/tags.php"); require_once("include/files.php"); -define("DFRN_TOP_LEVEL", 0); -define("DFRN_REPLY", 1); -define("DFRN_REPLY_RC", 2); - class dfrn2 { + const DFRN_TOP_LEVEL = 0; + const DFRN_REPLY = 1; + const DFRN_REPLY_RC = 2; + /** * @brief Add new birthday event for this person * @@ -58,14 +58,13 @@ class dfrn2 { * * @param object $xpath XPath object * @param object $context In which context should the data be searched - * @param array $importer Record of the importer contact + * @param array $importer Record of the importer user mixed with contact of the content * @param string $element Element name from which the data is fetched - * @param array $contact The updated contact record of the author * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well * * @return Returns an array with relevant data of the author */ - private function fetchauthor($xpath, $context, $importer, $element, $contact, $onlyfetch) { + private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch) { $author = array(); $author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue; @@ -80,8 +79,8 @@ class dfrn2 { $author["contact-id"] = $r[0]["id"]; $author["network"] = $r[0]["network"]; } else { - $author["contact-id"] = $contact["id"]; - $author["network"] = $contact["network"]; + $author["contact-id"] = $importer["id"]; + $author["network"] = $importer["network"]; $onlyfetch = true; } @@ -623,7 +622,7 @@ class dfrn2 { } } - private function process_entry($header, $xpath, $entry, $importer, $contact) { + private function process_entry($header, $xpath, $entry, $importer) { logger("Processing entries"); @@ -633,27 +632,29 @@ class dfrn2 { $item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue; // Fetch the owner - $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", $contact, true); + $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", true); $item["owner-name"] = $owner["name"]; $item["owner-link"] = $owner["link"]; $item["owner-avatar"] = $owner["avatar"]; - if ($header["contact-id"] != $owner["contact-id"]) - $item["contact-id"] = $owner["contact-id"]; + // At the moment we trust the importer array + //if ($header["contact-id"] != $owner["contact-id"]) + // $item["contact-id"] = $owner["contact-id"]; if (($header["network"] != $owner["network"]) AND ($owner["network"] != "")) $item["network"] = $owner["network"]; // fetch the author - $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", $contact, true); + $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", true); $item["author-name"] = $author["name"]; $item["author-link"] = $author["link"]; $item["author-avatar"] = $author["avatar"]; - if ($header["contact-id"] != $author["contact-id"]) - $item["contact-id"] = $author["contact-id"]; + // At the moment we trust the importer array + //if ($header["contact-id"] != $author["contact-id"]) + // $item["contact-id"] = $author["contact-id"]; if (($header["network"] != $author["network"]) AND ($author["network"] != "")) $item["network"] = $author["network"]; @@ -948,7 +949,7 @@ class dfrn2 { } } - private function process_deletion($header, $xpath, $deletion, $importer, $contact_id) { + private function process_deletion($header, $xpath, $deletion, $importer) { logger("Processing deletions"); @@ -963,7 +964,7 @@ class dfrn2 { else $when = datetime_convert("UTC", "UTC", "now", "Y-m-d H:i:s"); - if (!$uri OR !$contact_id) + if (!$uri OR !$importer["id"]) return false; /// @todo Only select the used fields @@ -971,10 +972,10 @@ class dfrn2 { WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", dbesc($uri), intval($importer["uid"]), - intval($contact_id) + intval($importer["id"]) ); if(!count($r)) { - logger("Item with uri ".$uri." from contact ".$contact_id." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG); + logger("Item with uri ".$uri." from contact ".$importer["id"]." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG); return; } else { @@ -1079,11 +1080,25 @@ class dfrn2 { } } - function import($xml,$importer, &$contact) { + /** + * @brief Imports a DFRN message + * + * @param text $xml The DFRN message + * @param array $importer Record of the importer user mixed with contact of the content + * @param bool $sort_by_date Is used when feeds are polled + */ + function import($xml,$importer, $sort_by_date = false) { if ($xml == "") return; + if($importer["readonly"]) { + // We aren't receiving stuff from this person. But we will quietly ignore them + // rather than a blatant "go away" message. + logger('ignoring contact '.$importer["id"]); + return; + } + $doc = new DOMDocument(); @$doc->loadXML($xml); @@ -1099,12 +1114,6 @@ class dfrn2 { $xpath->registerNamespace("ostatus", NAMESPACE_OSTATUS); $xpath->registerNamespace("statusnet", NAMESPACE_STATUSNET); - /// @todo Do we need this? - if (!$contact) { - $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `self`", intval($importer["uid"])); - $contact = $r[0]; - } - $header = array(); $header["uid"] = $importer["uid"]; $header["network"] = NETWORK_DFRN; @@ -1115,22 +1124,17 @@ class dfrn2 { // Update the contact table if the data has changed // Only the "dfrn:owner" in the head section contains all data - $dfrn_owner = self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", $contact, false); + self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false); - logger("Import DFRN message for user ".$importer["uid"]." from contact ".$contact["id"]." ".print_r($dfrn_owner, true)." - ".print_r($contact, true), LOGGER_DEBUG); - - //if (!$dfrn_owner["found"]) { - // logger("Author doesn't seem to be known by us. UID: ".$importer["uid"]." Contact: ".$dfrn_owner["contact-id"]." - ".print_r($dfrn_owner, true)); - // return; - //} + logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG); // is it a public forum? Private forums aren't supported by now with this method $forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue); - if ($forum AND ($dfrn_owner["contact-id"] != 0)) + if ($forum) q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d", intval($forum), intval($forum), - intval($dfrn_owner["contact-id"]) + intval($importer["id"]) ); $mails = $xpath->query("/atom:feed/dfrn:mail"); @@ -1147,11 +1151,11 @@ class dfrn2 { $deletions = $xpath->query("/atom:feed/at:deleted-entry"); foreach ($deletions AS $deletion) - self::process_deletion($header, $xpath, $deletion, $importer, $dfrn_owner["contact-id"]); + self::process_deletion($header, $xpath, $deletion, $importer); $entries = $xpath->query("/atom:feed/atom:entry"); foreach ($entries AS $entry) - self::process_entry($header, $xpath, $entry, $importer, $contact); + self::process_entry($header, $xpath, $entry, $importer); } } ?> diff --git a/include/items.php b/include/items.php index 0e98e8f19e..f1291d6490 100644 --- a/include/items.php +++ b/include/items.php @@ -1696,13 +1696,29 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) return; } // dfrn-test -// if ($contact['network'] === NETWORK_DFRN) { -// logger("Consume DFRN messages", LOGGER_DEBUG); -// logger("dfrn-test"); -// dfrn2::import($xml,$importer, $contact); -// return; -// } +/* + if ($contact['network'] === NETWORK_DFRN) { + logger("Consume DFRN messages", LOGGER_DEBUG); + logger("dfrn-test"); + $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`, + `contact`.`pubkey` AS `cpubkey`, + `contact`.`prvkey` AS `cprvkey`, + `contact`.`thumb` AS `thumb`, + `contact`.`url` as `url`, + `contact`.`name` as `senderName`, + `user`.* + FROM `contact` + LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid` + WHERE `contact`.`id` = %d AND `user`.`uid` = %d", + dbesc($contact["id"], $importer["uid"]); + ); + if ($r) { + dfrn2::import($xml,$r[0], true); + return; + } + } +*/ // Test - remove before flight //if ($pass < 2) { // $tempfile = tempnam(get_temppath(), "dfrn-consume-"); @@ -2408,7 +2424,7 @@ function item_is_remote_self($contact, &$datarray) { function local_delivery($importer,$data) { // dfrn-Test - //return dfrn2::import($data, $importer, $contact); + //return dfrn2::import($data, $importer); require_once('library/simplepie/simplepie.inc'); From e11ff25de2122fd619a68152af8f7a66cfb11ca1 Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Wed, 3 Feb 2016 23:22:45 +0100 Subject: [PATCH 026/273] fix empty ACL window on desktop --- view/templates/acl_selector.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/templates/acl_selector.tpl b/view/templates/acl_selector.tpl index bb76135067..7c68a73064 100644 --- a/view/templates/acl_selector.tpl +++ b/view/templates/acl_selector.tpl @@ -30,7 +30,7 @@ $(document).ready(function() { baseurl+"/acl", [ {{$allowcid}},{{$allowgid}},{{$denycid}},{{$denygid}} ], {{$features.aclautomention}}, - {{$APP->is_mobile}} + {{if $APP->is_mobile}}true{{else}}false{{/if}} ); } }); From 5f6ba00408ca0f28b2d36d799fa62d37c72333a8 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 4 Feb 2016 10:19:13 +0100 Subject: [PATCH 027/273] Added documentation, fixed some bug --- include/import-dfrn.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 981e596432..0abf92feb4 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -776,7 +776,7 @@ class dfrn2 { $item["parent-uri"] = $attributes->textContent; // Get the type of the item (Top level post, reply or remote reply) - $entrytype = get_entry_type($importer, $item); + $entrytype = self::get_entry_type($importer, $item); // Now assign the rest of the values that depend on the type of the message if ($entrytype == DFRN_REPLY_RC) { @@ -892,6 +892,8 @@ class dfrn2 { if($posted_id) { + logger("Reply was stored with id ".$posted_id, LOGGER_DEBUG); + $item["id"] = $posted_id; $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", @@ -918,6 +920,7 @@ class dfrn2 { } if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) { + logger("Notifying followers about comment ".$posted_id, LOGGER_DEBUG); proc_run("php", "include/notifier.php", "comment-import", $posted_id); } @@ -944,6 +947,8 @@ class dfrn2 { $posted_id = item_store($item, false, $notify); + logger("Item was stored with id ".$posted_id, LOGGER_DEBUG); + if(stristr($item["verb"],ACTIVITY_POKE)) self::do_poke($item, $importer, $posted_id); } @@ -981,7 +986,7 @@ class dfrn2 { $item = $r[0]; - $entrytype = get_entry_type($importer, $item); + $entrytype = self::get_entry_type($importer, $item); if(!$item["deleted"]) logger('deleting item '.$item["id"].' uri='.$uri, LOGGER_DEBUG); @@ -1074,8 +1079,10 @@ class dfrn2 { } // if this is a relayed delete, propagate it to other recipients - if($entrytype == DFRN_REPLY_RC) + if($entrytype == DFRN_REPLY_RC) { + logger("Notifying followers about deletion of post ".$item["id"], LOGGER_DEBUG); proc_run("php", "include/notifier.php","drop", $item["id"]); + } } } } From 3a649047df15c5edad55572c62c2e3b6a35084db Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 4 Feb 2016 12:51:34 +0100 Subject: [PATCH 028/273] DFRN: Contact-id is now stored depending on the author/Some more bugfixes --- include/import-dfrn.php | 75 +++++++++++++++++++++-------------------- include/items.php | 4 +-- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 0abf92feb4..87b2273ef4 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -1,16 +1,4 @@ <?php -/* -require_once("include/Contact.php"); -require_once("include/html2bbcode.php"); -require_once("include/bbcode.php"); -require_once("mod/share.php"); -require_once("include/Photo.php"); -require_once("include/Scrape.php"); -require_once("include/follow.php"); -require_once("include/api.php"); -require_once("mod/proxy.php"); -*/ - require_once("include/enotify.php"); require_once("include/threads.php"); require_once("include/socgraph.php"); @@ -196,25 +184,37 @@ class dfrn2 { unset($fields["id"]); unset($fields["uid"]); + unset($fields["avatar-date"]); + unset($fields["name-date"]); + unset($fields["uri-date"]); + + // Update check for this field has to be done differently + $datefields = array("name-date", "uri-date"); + foreach ($datefields AS $field) + if (strtotime($contact[$field]) > strtotime($r[0][$field])) + $update = true; foreach ($fields AS $field => $data) - if ($contact[$field] != $r[0][$field]) + if ($contact[$field] != $r[0][$field]) { + logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG); $update = true; + } if ($update) { logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', - `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s' - `avatar-date` = '%s', `name-date` = '%s', `uri-date` = '%s' + `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s', + `name-date` = '%s', `uri-date` = '%s' WHERE `id` = %d AND `network` = '%s'", dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]), - dbesc($contact["bd"]), dbesc($contact["avatar-date"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]), + dbesc($contact["bd"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]), intval($contact["id"]), dbesc($contact["network"])); } - update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"], ($contact["avatar-date"] != $r[0]["avatar-date"])); + update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"], + (strtotime($contact["avatar-date"]) > strtotime($r[0]["avatar-date"]))); $contact["generation"] = 2; $contact["photo"] = $author["avatar"]; @@ -301,6 +301,8 @@ class dfrn2 { ); notification($notif_params); + + logger("Mail is processed, notification was sent."); } private function process_suggestion($xpath, $suggestion, $importer) { @@ -638,13 +640,6 @@ class dfrn2 { $item["owner-link"] = $owner["link"]; $item["owner-avatar"] = $owner["avatar"]; - // At the moment we trust the importer array - //if ($header["contact-id"] != $owner["contact-id"]) - // $item["contact-id"] = $owner["contact-id"]; - - if (($header["network"] != $owner["network"]) AND ($owner["network"] != "")) - $item["network"] = $owner["network"]; - // fetch the author $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", true); @@ -652,13 +647,6 @@ class dfrn2 { $item["author-link"] = $author["link"]; $item["author-avatar"] = $author["avatar"]; - // At the moment we trust the importer array - //if ($header["contact-id"] != $author["contact-id"]) - // $item["contact-id"] = $author["contact-id"]; - - if (($header["network"] != $author["network"]) AND ($author["network"] != "")) - $item["network"] = $author["network"]; - $item["title"] = $xpath->query("atom:title/text()", $entry)->item(0)->nodeValue; $item["created"] = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; @@ -779,16 +767,31 @@ class dfrn2 { $entrytype = self::get_entry_type($importer, $item); // Now assign the rest of the values that depend on the type of the message - if ($entrytype == DFRN_REPLY_RC) { + if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { if (!isset($item["object-type"])) $item["object-type"] = ACTIVITY_OBJ_COMMENT; + if ($item["contact-id"] != $owner["contact-id"]) + $item["contact-id"] = $owner["contact-id"]; + + if (($item["network"] != $owner["network"]) AND ($owner["network"] != "")) + $item["network"] = $owner["network"]; + + if ($item["contact-id"] != $author["contact-id"]) + $item["contact-id"] = $author["contact-id"]; + + if (($item["network"] != $author["network"]) AND ($author["network"] != "")) + $item["network"] = $author["network"]; + } + + if ($entrytype == DFRN_REPLY_RC) { $item["type"] = "remote-comment"; $item["wall"] = 1; - } elseif ($entrytype == DFRN_REPLY) { - if (!isset($item["object-type"])) - $item["object-type"] = ACTIVITY_OBJ_COMMENT; } else { + // The Diaspora signature is only stored in replies + // Since this isn't a field in the item table this would create a bug when inserting this in the item table + unset($item["dsprsig"]); + if (!isset($item["object-type"])) $item["object-type"] = ACTIVITY_OBJ_NOTE; @@ -892,7 +895,7 @@ class dfrn2 { if($posted_id) { - logger("Reply was stored with id ".$posted_id, LOGGER_DEBUG); + logger("Reply from contact ".$item["contact-id"]." was stored with id ".$posted_id, LOGGER_DEBUG); $item["id"] = $posted_id; diff --git a/include/items.php b/include/items.php index f1291d6490..0ec645cc7a 100644 --- a/include/items.php +++ b/include/items.php @@ -17,7 +17,7 @@ require_once('include/feed.php'); require_once('include/Contact.php'); require_once('mod/share.php'); require_once('include/enotify.php'); -//require_once('include/import-dfrn.php'); +require_once('include/import-dfrn.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); @@ -2424,7 +2424,7 @@ function item_is_remote_self($contact, &$datarray) { function local_delivery($importer,$data) { // dfrn-Test - //return dfrn2::import($data, $importer); + return dfrn2::import($data, $importer); require_once('library/simplepie/simplepie.inc'); From 9a54afa629acc58a8e1df9dd48a2dd1f718b8a77 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Thu, 4 Feb 2016 14:47:20 +0100 Subject: [PATCH 029/273] Forum class - rename Class CamelCase and rename some methods --- include/{forum.php => Forum.php} | 12 ++++++------ include/identity.php | 4 ++-- mod/network.php | 4 ++-- mod/ping.php | 4 ++-- view/theme/vier/theme.php | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) rename include/{forum.php => Forum.php} (92%) diff --git a/include/forum.php b/include/Forum.php similarity index 92% rename from include/forum.php rename to include/Forum.php index 290f0134a1..847affa875 100644 --- a/include/forum.php +++ b/include/Forum.php @@ -8,7 +8,7 @@ /** * @brief This class handles functions related to the forum functionality */ -class forum { +class Forum { /** * @brief Function to list all forums a user is connected with @@ -27,7 +27,7 @@ class forum { * 'id' => number of the key from the array * 'micro' => contact photo in format micro */ - public static function get_forumlist($uid, $showhidden = true, $lastitem, $showprivate = false) { + public static function get_list($uid, $showhidden = true, $lastitem, $showprivate = false) { $forumlist = array(); @@ -72,7 +72,7 @@ class forum { * The contact id which is used to mark a forum as "selected" * @return string */ - public static function widget_forumlist($uid,$cid = 0) { + public static function widget($uid,$cid = 0) { if(! intval(feature_enabled(local_user(),'forumlist_widget'))) return; @@ -82,7 +82,7 @@ class forum { //sort by last updated item $lastitem = true; - $contacts = self::get_forumlist($uid,true,$lastitem, true); + $contacts = self::get_list($uid,true,$lastitem, true); $total = count($contacts); $visible_forums = 10; @@ -131,7 +131,7 @@ class forum { * @return string * */ - public static function forumlist_profile_advanced($uid) { + public static function profile_advanced($uid) { $profile = intval(feature_enabled($uid,'forumlist_profile')); if(! $profile) @@ -145,7 +145,7 @@ class forum { //don't sort by last updated item $lastitem = false; - $contacts = self::get_forumlist($uid,false,$lastitem,false); + $contacts = self::get_list($uid,false,$lastitem,false); $total_shown = 0; diff --git a/include/identity.php b/include/identity.php index 87c91e6dcc..a6932f0911 100644 --- a/include/identity.php +++ b/include/identity.php @@ -3,7 +3,7 @@ * @file include/identity.php */ -require_once('include/forum.php'); +require_once('include/Forum.php'); require_once('include/bbcode.php'); require_once("mod/proxy.php"); @@ -655,7 +655,7 @@ function advanced_profile(&$a) { //show subcribed forum if it is enabled in the usersettings if (feature_enabled($uid,'forumlist_profile')) { - $profile['forumlist'] = array( t('Forums:'), forum::forumlist_profile_advanced($uid)); + $profile['forumlist'] = array( t('Forums:'), Forum::profile_advanced($uid)); } if ($a->profile['uid'] == local_user()) diff --git a/mod/network.php b/mod/network.php index 151384cd67..b72d72d78f 100644 --- a/mod/network.php +++ b/mod/network.php @@ -114,7 +114,7 @@ function network_init(&$a) { require_once('include/group.php'); require_once('include/contact_widgets.php'); require_once('include/items.php'); - require_once('include/forum.php'); + require_once('include/Forum.php'); if(! x($a->page,'aside')) $a->page['aside'] = ''; @@ -148,7 +148,7 @@ function network_init(&$a) { } $a->page['aside'] .= (feature_enabled(local_user(),'groups') ? group_side('network/0','network','standard',$group_id) : ''); - $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? forum::widget_forumlist(local_user(),$cid) : ''); + $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? Forum::widget(local_user(),$cid) : ''); $a->page['aside'] .= posted_date_widget($a->get_baseurl() . '/network',local_user(),false); $a->page['aside'] .= networks_widget($a->get_baseurl(true) . '/network',(x($_GET, 'nets') ? $_GET['nets'] : '')); $a->page['aside'] .= saved_searches($search); diff --git a/mod/ping.php b/mod/ping.php index 3d34e2fe62..2abad13b0b 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -1,7 +1,7 @@ <?php require_once("include/datetime.php"); require_once('include/bbcode.php'); -require_once('include/forum.php'); +require_once('include/Forum.php'); require_once('include/group.php'); require_once("mod/proxy.php"); @@ -96,7 +96,7 @@ function ping_init(&$a) { } if(intval(feature_enabled(local_user(),'forumlist_widget'))) { - $forums_unseen = forum::count_unseen_items(); + $forums_unseen = Forum::count_unseen_items(); } } diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php index 93a6d5ce08..cd2c31f188 100644 --- a/view/theme/vier/theme.php +++ b/view/theme/vier/theme.php @@ -220,7 +220,7 @@ function vier_community_info() { //Community_Pages at right_aside if($show_pages AND local_user()) { - require_once('include/forum.php'); + require_once('include/Forum.php'); if(x($_GET['cid']) && intval($_GET['cid']) != 0) $cid = $_GET['cid']; @@ -228,7 +228,7 @@ function vier_community_info() { //sort by last updated item $lastitem = true; - $contacts = forum::get_forumlist($a->user['uid'],true,$lastitem, true); + $contacts = Forum::get_list($a->user['uid'],true,$lastitem, true); $total = count($contacts); $visible_forums = 10; From 52dbb0b4a2fd4aa4d747605e329002334a26dd72 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Thu, 4 Feb 2016 14:51:54 +0100 Subject: [PATCH 030/273] Forum class - rename Class CamelCase and rename some methods --- include/{Forum.php => ForumManager.php} | 2 +- include/identity.php | 4 ++-- mod/network.php | 4 ++-- mod/ping.php | 4 ++-- view/theme/vier/theme.php | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) rename include/{Forum.php => ForumManager.php} (99%) diff --git a/include/Forum.php b/include/ForumManager.php similarity index 99% rename from include/Forum.php rename to include/ForumManager.php index 847affa875..49417d1831 100644 --- a/include/Forum.php +++ b/include/ForumManager.php @@ -8,7 +8,7 @@ /** * @brief This class handles functions related to the forum functionality */ -class Forum { +class ForumManager { /** * @brief Function to list all forums a user is connected with diff --git a/include/identity.php b/include/identity.php index a6932f0911..ec66225d0f 100644 --- a/include/identity.php +++ b/include/identity.php @@ -3,7 +3,7 @@ * @file include/identity.php */ -require_once('include/Forum.php'); +require_once('include/ForumManager.php'); require_once('include/bbcode.php'); require_once("mod/proxy.php"); @@ -655,7 +655,7 @@ function advanced_profile(&$a) { //show subcribed forum if it is enabled in the usersettings if (feature_enabled($uid,'forumlist_profile')) { - $profile['forumlist'] = array( t('Forums:'), Forum::profile_advanced($uid)); + $profile['forumlist'] = array( t('Forums:'), ForumManager::profile_advanced($uid)); } if ($a->profile['uid'] == local_user()) diff --git a/mod/network.php b/mod/network.php index b72d72d78f..0010a3d824 100644 --- a/mod/network.php +++ b/mod/network.php @@ -114,7 +114,7 @@ function network_init(&$a) { require_once('include/group.php'); require_once('include/contact_widgets.php'); require_once('include/items.php'); - require_once('include/Forum.php'); + require_once('include/ForumManager.php'); if(! x($a->page,'aside')) $a->page['aside'] = ''; @@ -148,7 +148,7 @@ function network_init(&$a) { } $a->page['aside'] .= (feature_enabled(local_user(),'groups') ? group_side('network/0','network','standard',$group_id) : ''); - $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? Forum::widget(local_user(),$cid) : ''); + $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? ForumManager::widget(local_user(),$cid) : ''); $a->page['aside'] .= posted_date_widget($a->get_baseurl() . '/network',local_user(),false); $a->page['aside'] .= networks_widget($a->get_baseurl(true) . '/network',(x($_GET, 'nets') ? $_GET['nets'] : '')); $a->page['aside'] .= saved_searches($search); diff --git a/mod/ping.php b/mod/ping.php index 2abad13b0b..577a2c6c82 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -1,7 +1,7 @@ <?php require_once("include/datetime.php"); require_once('include/bbcode.php'); -require_once('include/Forum.php'); +require_once('include/ForumManager.php'); require_once('include/group.php'); require_once("mod/proxy.php"); @@ -96,7 +96,7 @@ function ping_init(&$a) { } if(intval(feature_enabled(local_user(),'forumlist_widget'))) { - $forums_unseen = Forum::count_unseen_items(); + $forums_unseen = ForumManager::count_unseen_items(); } } diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php index cd2c31f188..c2669f5a93 100644 --- a/view/theme/vier/theme.php +++ b/view/theme/vier/theme.php @@ -220,7 +220,7 @@ function vier_community_info() { //Community_Pages at right_aside if($show_pages AND local_user()) { - require_once('include/Forum.php'); + require_once('include/ForumManager.php'); if(x($_GET['cid']) && intval($_GET['cid']) != 0) $cid = $_GET['cid']; @@ -228,7 +228,7 @@ function vier_community_info() { //sort by last updated item $lastitem = true; - $contacts = Forum::get_list($a->user['uid'],true,$lastitem, true); + $contacts = ForumManager::get_list($a->user['uid'],true,$lastitem, true); $total = count($contacts); $visible_forums = 10; From 0390001d4e9244f114e3021c6ae243cdd77d99c9 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 4 Feb 2016 15:53:22 +0100 Subject: [PATCH 031/273] Added some more logging --- include/import-dfrn.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 87b2273ef4..c4b3fed361 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -95,8 +95,6 @@ class dfrn2 { $author["avatar"] = current($avatarlist); } - //$onlyfetch = true; // Test - if ($r AND !$onlyfetch) { // When was the last change to name or uri? @@ -481,11 +479,13 @@ class dfrn2 { } private function update_content($current, $item, $importer, $entrytype) { + $changed = false; + if (edited_timestamp_is_newer($current, $item)) { // do not accept (ignore) an earlier edit than one we currently have. if(datetime_convert("UTC","UTC",$item["edited"]) < $current["edited"]) - return; + return(false); $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", dbesc($item["title"]), @@ -499,6 +499,8 @@ class dfrn2 { create_tags_from_itemuri($item["uri"], $importer["importer_uid"]); update_thread_uri($item["uri"], $importer["importer_uid"]); + $changed = true; + if ($entrytype == DFRN_REPLY_RC) proc_run("php", "include/notifier.php","comment-import", $current["id"]); } @@ -517,6 +519,7 @@ class dfrn2 { intval($importer["importer_uid"]) ); } + return $changed; } private function get_entry_type($importer, $item) { @@ -812,7 +815,8 @@ class dfrn2 { if(count($r)) $ev["id"] = $r[0]["id"]; $xyz = event_store($ev); - return; + logger("Event ".$ev["id"]." was stored", LOGGER_DEBUG); + return; } } } @@ -824,13 +828,18 @@ class dfrn2 { // Update content if 'updated' changes if(count($r)) { - self::update_content($r[0], $item, $importer, $entrytype); + if (self::update_content($r[0], $item, $importer, $entrytype)) + logger("Item ".$item["uri"]." was updated.", LOGGER_DEBUG); + else + logger("Item ".$item["uri"]." already existed.", LOGGER_DEBUG); return; } if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { - if($importer["rel"] == CONTACT_IS_FOLLOWER) + if($importer["rel"] == CONTACT_IS_FOLLOWER) { + logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); return; + } if(($item["verb"] === ACTIVITY_LIKE) || ($item["verb"] === ACTIVITY_DISLIKE) @@ -931,6 +940,7 @@ class dfrn2 { } } else { if(!link_compare($item["owner-link"],$importer["url"])) { + /// @todo Check if this is really used // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, // but otherwise there's a possible data mixup on the sender's system. // the tgroup delivery code called from item_store will correct it if it's a forum, From 12c6ba9081e05ecb2b9ebcbb16aeb0b7dfc0eb55 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Thu, 4 Feb 2016 16:21:40 +0100 Subject: [PATCH 032/273] vier: remove some hardcoded css to be more mobile friendly --- view/theme/vier/style.css | 67 +++++++++++---------------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index 7d7e2683a4..8c03277976 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -1583,63 +1583,34 @@ section.minimal { width: calc(100% - 165px); } -.wall-item-container.comment .wall-item-content { - max-width: 585px; -} -.wall-item-container.thread_level_3 .wall-item-content { - max-width: 570px; -} -.wall-item-container.thread_level_4 .wall-item-content { - max-width: 555px; -} -.wall-item-container.thread_level_5 .wall-item-content { - max-width: 540px; -} -.wall-item-container.thread_level_6 .wall-item-content { - max-width: 525px; -} +.wall-item-container.comment .wall-item-content, +.wall-item-container.thread_level_3 .wall-item-content, +.wall-item-container.thread_level_4 .wall-item-content, +.wall-item-container.thread_level_5 .wall-item-content, +.wall-item-container.thread_level_6 .wall-item-content, .wall-item-container.thread_level_7 .wall-item-content { - max-width: 510px; + max-width: calc(100% - 1px); } -.children .wall-item-comment-wrapper textarea { - width: 570px; -} -.wall-item-container.thread_level_3 .wall-item-comment-wrapper textarea { - width: 555px; -} -.wall-item-container.thread_level_4 .wall-item-comment-wrapper textarea { - width: 540px; -} -.wall-item-container.thread_level_5 .wall-item-comment-wrapper textarea { - width: 525px; -} -.wall-item-container.thread_level_6 .wall-item-comment-wrapper textarea { - width: 510px; -} +.children .wall-item-comment-wrapper textarea, +.wall-item-container.thread_level_3 .wall-item-comment-wrapper textarea, +.wall-item-container.thread_level_4 .wall-item-comment-wrapper textarea, +.wall-item-container.thread_level_5 .wall-item-comment-wrapper textarea, +.wall-item-container.thread_level_6 .wall-item-comment-wrapper textarea, .wall-item-container.thread_level_7 .wall-item-comment-wrapper textarea { - width: 495px; + width: calc(100% - 11px); } -.children .wall-item-bottom .comment-edit-preview { - width: 575px; -} -.wall-item-container.thread_level_3 .wall-item-bottom .comment-edit-preview { - width: 560px; -} -.wall-item-container.thread_level_4 .wall-item-bottom .comment-edit-preview { - width: 545px; -} -.wall-item-container.thread_level_5 .wall-item-bottom .comment-edit-preview { - width: 530px; -} -.wall-item-container.thread_level_6 .wall-item-bottom .comment-edit-preview { - width: 515px; -} +.children .wall-item-bottom .comment-edit-preview, +.wall-item-container.thread_level_3 .wall-item-bottom .comment-edit-preview, +.wall-item-container.thread_level_4 .wall-item-bottom .comment-edit-preview, +.wall-item-container.thread_level_5 .wall-item-bottom .comment-edit-preview, +.wall-item-container.thread_level_6 .wall-item-bottom .comment-edit-preview, .wall-item-container.thread_level_7 .wall-item-bottom .comment-edit-preview { - width: 500px; + width: calc(100% - 6px); } + .wall-item-container.comment .contact-photo { width: 32px; height: 32px; From a75be65860391ea81e91ba28d2b4047c8b3bcc02 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Thu, 4 Feb 2016 16:45:39 +0100 Subject: [PATCH 033/273] vier: little float fix --- view/theme/vier/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index 8c03277976..39d2af680b 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -1042,6 +1042,9 @@ aside { box-shadow: 1px 2px 0px 0px #D8D8D8; color: #737373; } +aside .vcard .tool { + clear: both; +} aside .vcard .fn { font-size: 18px; font-weight: bold; @@ -1050,7 +1053,6 @@ aside .vcard .fn { } aside .vcard .title { margin-bottom: 5px; - float: left; } aside .vcard dl { height: auto; From b85369a03bcc10c8f6860be11e129c03dd49c141 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 4 Feb 2016 18:58:33 +0100 Subject: [PATCH 034/273] Vier: The font "system" can make the system look ugly --- view/theme/vier/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index e0abee837c..7d56f6c402 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -269,7 +269,7 @@ div.pager { /* global */ body { /* font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; */ - font-family: system,-apple-system,".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande",Helvetica,Arial,sans-serif; + font-family: -apple-system,".SFNSText-Regular","San Francisco","Roboto","Segoe UI","Helvetica Neue","Lucida Grande",Helvetica,Arial,sans-serif; font-size: 14px; /* font-size: 13px; line-height: 19.5px; */ From acefb846810434a77335862bfb9bd1294c079a9c Mon Sep 17 00:00:00 2001 From: Andrej Stieben <e-mail@stieben.de> Date: Thu, 4 Feb 2016 19:37:06 +0100 Subject: [PATCH 035/273] Improved documentation formatting (of Plugins.md) --- doc/Plugins.md | 101 +++++++++++++++++++++++----------------------- doc/de/Plugins.md | 75 ++++++++++++++++++---------------- 2 files changed, 92 insertions(+), 84 deletions(-) diff --git a/doc/Plugins.md b/doc/Plugins.md index 24d403e1f6..a30a3f4a71 100644 --- a/doc/Plugins.md +++ b/doc/Plugins.md @@ -1,5 +1,7 @@ Friendica Addon/Plugin development -========================== +============== + +* [Home](help) Please see the sample addon 'randplace' for a working example of using some of these features. Addons work by intercepting event hooks - which must be registered. @@ -16,12 +18,12 @@ Future extensions may provide for "setup" amd "remove". Plugins should contain a comment block with the four following parameters: - /* - * Name: My Great Plugin - * Description: This is what my plugin does. It's really cool - * Version: 1.0 - * Author: John Q. Public <john@myfriendicasite.com> - */ + /* + * Name: My Great Plugin + * Description: This is what my plugin does. It's really cool. + * Version: 1.0 + * Author: John Q. Public <john@myfriendicasite.com> + */ Register your plugin hooks during installation. @@ -45,7 +47,7 @@ Your hook callback functions will be called with at least one and possibly two a If you wish to make changes to the calling data, you must declare them as reference variables (with '&') during function declaration. -###$a +#### $a $a is the Friendica 'App' class. It contains a wealth of information about the current state of Friendica: @@ -56,13 +58,13 @@ It contains a wealth of information about the current state of Friendica: It is recommeded you call this '$a' to match its usage elsewhere. -###$b +#### $b $b can be called anything you like. This is information specific to the hook currently being processed, and generally contains information that is being immediately processed or acted on that you can use, display, or alter. Remember to declare it with '&' if you wish to alter it. Modules --------- +--- Plugins/addons may also act as "modules" and intercept all page requests for a given URL path. In order for a plugin to act as a module it needs to define a function "plugin_name_module()" which takes no arguments and needs not do anything. @@ -72,15 +74,15 @@ These are parsed into an array $a->argv, with a corresponding $a->argc indicatin So http://my.web.site/plugin/arg1/arg2 would look for a module named "plugin" and pass its module functions the $a App structure (which is available to many components). This will include: - $a->argc = 3 - $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); + $a->argc = 3 + $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); Your module functions will often contain the function plugin_name_content(&$a), which defines and returns the page body content. They may also contain plugin_name_post(&$a) which is called before the _content function and typically handles the results of POST forms. You may also have plugin_name_init(&$a) which is called very early on and often does module initialisation. Templates ----------- +--- If your plugin needs some template, you can use the Friendica template system. Friendica uses [smarty3](http://www.smarty.net/) as a template engine. @@ -104,140 +106,140 @@ See also the wiki page [Quick Template Guide](https://github.com/friendica/frien Current hooks ------------- -###'authenticate' +### 'authenticate' 'authenticate' is called when a user attempts to login. $b is an array containing: - 'username' => the supplied username - 'password' => the supplied password + 'username' => the supplied username + 'password' => the supplied password 'authenticated' => set this to non-zero to authenticate the user. 'user_record' => successful authentication must also return a valid user record from the database -###'logged_in' +### 'logged_in' 'logged_in' is called after a user has successfully logged in. $b contains the $a->user array. -###'display_item' +### 'display_item' 'display_item' is called when formatting a post for display. $b is an array: 'item' => The item (array) details pulled from the database 'output' => the (string) HTML representation of this item prior to adding it to the page -###'post_local' +### 'post_local' * called when a status post or comment is entered on the local system * $b is the item array of the information to be stored in the database * Please note: body contents are bbcode - not HTML -###'post_local_end' +### 'post_local_end' * called when a local status post or comment has been stored on the local system * $b is the item array of the information which has just been stored in the database * Please note: body contents are bbcode - not HTML -###'post_remote' +### 'post_remote' * called when receiving a post from another source. This may also be used to post local activity or system generated messages. * $b is the item array of information to be stored in the database and the item body is bbcode. -###'settings_form' +### 'settings_form' * called when generating the HTML for the user Settings page * $b is the (string) HTML of the settings page before the final '</form>' tag. -###'settings_post' +### 'settings_post' * called when the Settings pages are submitted * $b is the $_POST array -###'plugin_settings' +### 'plugin_settings' * called when generating the HTML for the addon settings page * $b is the (string) HTML of the addon settings page before the final '</form>' tag. -###'plugin_settings_post' +### 'plugin_settings_post' * called when the Addon Settings pages are submitted * $b is the $_POST array -###'profile_post' +### 'profile_post' * called when posting a profile page * $b is the $_POST array -###'profile_edit' +### 'profile_edit' 'profile_edit' is called prior to output of profile edit page. $b is an array containing: 'profile' => profile (array) record from the database 'entry' => the (string) HTML of the generated entry -###'profile_advanced' +### 'profile_advanced' * called when the HTML is generated for the 'Advanced profile', corresponding to the 'Profile' tab within a person's profile page * $b is the (string) HTML representation of the generated profile * The profile array details are in $a->profile. -###'directory_item' +### 'directory_item' 'directory_item' is called from the Directory page when formatting an item for display. $b is an array: 'contact' => contact (array) record for the person from the database 'entry' => the (string) HTML of the generated entry -###'profile_sidebar_enter' +### 'profile_sidebar_enter' * called prior to generating the sidebar "short" profile for a page * $b is the person's profile array -###'profile_sidebar' +### 'profile_sidebar' 'profile_sidebar is called when generating the sidebar "short" profile for a page. $b is an array: 'profile' => profile (array) record for the person from the database 'entry' => the (string) HTML of the generated entry -###'contact_block_end' +### 'contact_block_end' is called when formatting the block of contacts/friends on a profile sidebar has completed. $b is an array: 'contacts' => array of contacts 'output' => the (string) generated HTML of the contact block -###'bbcode' +### 'bbcode' * called during conversion of bbcode to html * $b is a string converted text -###'html2bbcode' +### 'html2bbcode' * called during conversion of html to bbcode (e.g. remote message posting) * $b is a string converted text -###'page_header' +### 'page_header' * called after building the page navigation section * $b is a string HTML of nav region -###'personal_xrd' +### 'personal_xrd' 'personal_xrd' is called prior to output of personal XRD file. $b is an array: 'user' => the user record for the person 'xml' => the complete XML to be output -###'home_content' +### 'home_content' * called prior to output home page content, shown to unlogged users * $b is (string) HTML of section region -###'contact_edit' +### 'contact_edit' is called when editing contact details on an individual from the Contacts page. $b is an array: 'contact' => contact record (array) of target contact 'output' => the (string) generated HTML of the contact edit page -###'contact_edit_post' +### 'contact_edit_post' * called when posting the contact edit page. * $b is the $_POST array -###'init_1' +### 'init_1' * called just after DB has been opened and before session start * $b is not used or passed -###'page_end' +### 'page_end' * called after HTML content functions have completed * $b is (string) HTML of content div -###'avatar_lookup' +### 'avatar_lookup' 'avatar_lookup' is called when looking up the avatar. $b is an array: @@ -245,11 +247,11 @@ $b is an array: 'email' => email to look up the avatar for 'url' => the (string) generated URL of the avatar -###'emailer_send_prepare' +### 'emailer_send_prepare' 'emailer_send_prepare' called from Emailer::send() before building the mime message. $b is an array, params to Emailer::send() - 'fromName' => name of the sender + 'fromName' => name of the sender 'fromEmail' => email fo the sender 'replyTo' => replyTo address to direct responses 'toEmail' => destination email address @@ -258,20 +260,20 @@ $b is an array, params to Emailer::send() 'textVersion' => text only version of the message 'additionalMailHeader' => additions to the smtp mail header -###'emailer_send' +### 'emailer_send' is called before calling PHP's mail(). $b is an array, params to mail() - 'to' - 'subject' + 'to' + 'subject' 'body' 'headers' -###'nav_info' +### 'nav_info' is called after the navigational menu is build in include/nav.php. $b is an array containing $nav from nav.php. -###'template_vars' +### 'template_vars' is called before vars are passed to the template engine to render the page. The registered function can add,change or remove variables passed to template. $b is an array with: @@ -463,4 +465,3 @@ mod/cb.php: call_hooks('cb_afterpost'); mod/cb.php: call_hooks('cb_content', $o); mod/directory.php: call_hooks('directory_item', $arr); - diff --git a/doc/de/Plugins.md b/doc/de/Plugins.md index dcff41a4b6..11113843bb 100644 --- a/doc/de/Plugins.md +++ b/doc/de/Plugins.md @@ -1,27 +1,27 @@ -**Friendica Addon/Plugin-Entwicklung** +Friendica Addon/Plugin-Entwicklung ============== * [Zur Startseite der Hilfe](help) -Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an. -Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren. -Addons arbeiten, indem sie Event Hooks abfangen. Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden +Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an. +Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren. +Addons arbeiten, indem sie Event Hooks abfangen. Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden -Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt. -Du kannst einen lesbaren Namen im Kommentarblock eintragen. -Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-/Plugin-Namen basieren; z.B. "plugin1name_install()". -Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Plugin benötigt. -Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Plugin nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen. +Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt. +Du kannst einen lesbaren Namen im Kommentarblock eintragen. +Jedes Addon muss beides beinhalten - eine Installations- und eine Deinstallationsfunktion, die auf dem Addon-/Plugin-Namen basieren; z.B. "plugin1name_install()". +Diese beiden Funktionen haben keine Argumente und sind dafür verantwortlich, Event Hooks zu registrieren und abzumelden (unregistering), die dein Plugin benötigt. +Die Installations- und Deinstallationsfunktionfunktionen werden auch ausgeführt (z.B. neu installiert), wenn sich das Plugin nach der Installation ändert - somit sollte deine Deinstallationsfunktion keine Daten zerstört und deine Installationsfunktion sollte bestehende Daten berücksichtigen. Zukünftige Extensions werden möglicherweise "Setup" und "Entfernen" anbieten. Plugins sollten einen Kommentarblock mit den folgenden vier Parametern enthalten: - /* - * Name: My Great Plugin - * Description: This is what my plugin does. It's really cool - * Version: 1.0 - * Author: John Q. Public <john@myfriendicasite.com> - */ + /* + * Name: My Great Plugin + * Description: This is what my plugin does. It's really cool + * Version: 1.0 + * Author: John Q. Public <john@myfriendicasite.com> + */ Registriere deine Plugin-Hooks während der Installation. @@ -29,45 +29,50 @@ Registriere deine Plugin-Hooks während der Installation. $hookname ist ein String und entspricht einem bekannten Friendica-Hook. -$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt. +$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt. Das *sollte* "addon/plugin_name/plugin_name.php' sein. $function ist ein String und der Name der Funktion, die ausgeführt wird, wenn der Hook aufgerufen wird. +Argumente +--- + Deine Hook-Callback-Funktion wird mit mindestens einem und bis zu zwei Argumenten aufgerufen function myhook_function(&$a, &$b) { } -Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren. +Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren. -$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs. -Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen. +$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs. +Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen. -$b kann frei benannt werden. -Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst. -Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst. +$b kann frei benannt werden. +Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst. +Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst. -**Module** +Module +--- -Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen. -Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss. +Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen. +Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss. -Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://my.web.site/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. -Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden. +Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://my.web.site/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. +Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden. So würde http://my.web.site/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst: - $a->argc = 3 - $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); + $a->argc = 3 + $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); -Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt. -Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. +Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt. +Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisert. -**Derzeitige Hooks:** +Derzeitige Hooks +--- **'authenticate'** - wird aufgerufen, wenn sich der User einloggt. $b ist ein Array @@ -180,6 +185,9 @@ Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wi - wird aufgerufen nachdem in include/nav,php der Inhalt des Navigations Menüs erzeugt wurde. - $b ist ein Array, das $nav wiederspiegelt. +Komplette Liste der Hook-Callbacks +--- + Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 14-Feb-2012 generiert): Bitte schau in die Quellcodes für Details zu Hooks, die oben nicht dokumentiert sind. boot.php: call_hooks('login_hook',$o); @@ -204,7 +212,7 @@ include/text.php: call_hooks('contact_block_end', $arr); include/text.php: call_hooks('smilie', $s); -include/text.php: call_hooks('prepare_body_init', $item); +include/text.php: call_hooks('prepare_body_init', $item); include/text.php: call_hooks('prepare_body', $prep_arr); @@ -359,4 +367,3 @@ mod/cb.php: call_hooks('cb_afterpost'); mod/cb.php: call_hooks('cb_content', $o); mod/directory.php: call_hooks('directory_item', $arr); - From 92a31344b5e8e0c01aec325874a86808f7bba8e5 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 4 Feb 2016 20:34:18 +0100 Subject: [PATCH 036/273] Events do work now. --- include/import-dfrn.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index c4b3fed361..edbbde98a0 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -5,6 +5,7 @@ require_once("include/socgraph.php"); require_once("include/items.php"); require_once("include/tags.php"); require_once("include/files.php"); +require_once("include/event.php"); class dfrn2 { @@ -702,6 +703,12 @@ class dfrn2 { $object = $xpath->query("activity:object", $entry)->item(0); $item["object"] = self::transform_activity($xpath, $object, "object"); + if (trim($item["object"]) != "") { + $r = parse_xml_string($item["object"], false); + if (isset($r->type)) + $item["object-type"] = $r->type; + } + $target = $xpath->query("activity:target", $entry)->item(0); $item["target"] = self::transform_activity($xpath, $target, "target"); @@ -790,7 +797,7 @@ class dfrn2 { if ($entrytype == DFRN_REPLY_RC) { $item["type"] = "remote-comment"; $item["wall"] = 1; - } else { + } elseif ($entrytype == DFRN_TOP_LEVEL) { // The Diaspora signature is only stored in replies // Since this isn't a field in the item table this would create a bug when inserting this in the item table unset($item["dsprsig"]); @@ -798,9 +805,11 @@ class dfrn2 { if (!isset($item["object-type"])) $item["object-type"] = ACTIVITY_OBJ_NOTE; - if ($item["object-type"] === ACTIVITY_OBJ_EVENT) { + if ($item["object-type"] == ACTIVITY_OBJ_EVENT) { + logger("Item ".$item["uri"]." seems to contain an event.", LOGGER_DEBUG); $ev = bbtoevent($item["body"]); if((x($ev, "desc") || x($ev, "summary")) && x($ev, "start")) { + logger("Event in item ".$item["uri"]." was found.", LOGGER_DEBUG); $ev["cid"] = $importer["id"]; $ev["uid"] = $importer["uid"]; $ev["uri"] = $item["uri"]; @@ -814,9 +823,10 @@ class dfrn2 { ); if(count($r)) $ev["id"] = $r[0]["id"]; - $xyz = event_store($ev); - logger("Event ".$ev["id"]." was stored", LOGGER_DEBUG); - return; + + $event_id = event_store($ev); + logger("Event ".$event_id." was stored", LOGGER_DEBUG); + return; } } } From 48e6ff21aa50b428a970c53f1f6618c4794fa043 Mon Sep 17 00:00:00 2001 From: Andrej Stieben <e-mail@stieben.de> Date: Thu, 4 Feb 2016 21:45:21 +0100 Subject: [PATCH 037/273] Added the possibility for themes to override core module functions --- doc/themes.md | 16 ++++++++++++++-- index.php | 11 ++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/doc/themes.md b/doc/themes.md index add44c776b..ec3a76ac28 100644 --- a/doc/themes.md +++ b/doc/themes.md @@ -59,7 +59,19 @@ The same rule applies to the JavaScript files found in they will be overwritten by files in - /view/theme/**your-theme-name**/js. + /view/theme/**your-theme-name**/js + +### Modules + +You have the freedom to override core modules found in + + /mod + +They will be overwritten by files in + + /view/theme/**your-theme-name**/mod + +Be aware that you can break things easily here if you don't know what you do. Also notice that you can override parts of the module – functions not defined in your theme module will be loaded from the core module. ## Expand an existing Theme @@ -288,4 +300,4 @@ The default file is in /view/default.php if you want to change it, say adding a 4th column for banners of your favourite FLOSS projects, place a new default.php file in your theme directory. -As with the theme.php file, you can use the properties of the $a variable with holds the friendica application to decide what content is displayed. \ No newline at end of file +As with the theme.php file, you can use the properties of the $a variable with holds the friendica application to decide what content is displayed. diff --git a/index.php b/index.php index bf926d1fe7..2b1053cc1b 100644 --- a/index.php +++ b/index.php @@ -233,7 +233,16 @@ if(strlen($a->module)) { } /** - * If not, next look for a 'standard' program module in the 'mod' directory + * If not, next look for module overrides by the theme + */ + + if((! $a->module_loaded) && (file_exists("view/theme/" . current_theme() . "/mod/{$a->module}.php"))) { + include_once("view/theme/" . current_theme() . "/mod/{$a->module}.php"); + // We will not set module_loaded to true to allow for partial overrides. + } + + /** + * Finally, look for a 'standard' program module in the 'mod' directory */ if((! $a->module_loaded) && (file_exists("mod/{$a->module}.php"))) { From 2bd36e562889803841588a276232ef5fa819730a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 4 Feb 2016 23:52:06 +0100 Subject: [PATCH 038/273] Feed should work now as well --- include/import-dfrn.php | 25 +++++++++++++++++++++---- include/items.php | 9 ++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index edbbde98a0..72d507bbf1 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -961,8 +961,10 @@ class dfrn2 { $item["owner-avatar"] = $importer["thumb"]; } - if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) + if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) { + logger("Contact ".$importer["id"]." is only follower and tgroup check was negative.", LOGGER_DEBUG); return; + } // This is my contact on another system, but it's really me. // Turn this into a wall post. @@ -1183,9 +1185,24 @@ class dfrn2 { foreach ($deletions AS $deletion) self::process_deletion($header, $xpath, $deletion, $importer); - $entries = $xpath->query("/atom:feed/atom:entry"); - foreach ($entries AS $entry) - self::process_entry($header, $xpath, $entry, $importer); + if (!$sort_by_date) { + $entries = $xpath->query("/atom:feed/atom:entry"); + foreach ($entries AS $entry) + self::process_entry($header, $xpath, $entry, $importer); + } else { + $newentries = array(); + $entries = $xpath->query("/atom:feed/atom:entry"); + foreach ($entries AS $entry) { + $created = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; + $newentries[strtotime($created)] = $entry; + } + + // Now sort after the publishing date + ksort($newentries); + + foreach ($newentries AS $entry) + self::process_entry($header, $xpath, $entry, $importer); + } } } ?> diff --git a/include/items.php b/include/items.php index 0ec645cc7a..52a1071f2d 100644 --- a/include/items.php +++ b/include/items.php @@ -1695,11 +1695,9 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) } return; } - // dfrn-test -/* + if ($contact['network'] === NETWORK_DFRN) { logger("Consume DFRN messages", LOGGER_DEBUG); - logger("dfrn-test"); $r = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`, `contact`.`pubkey` AS `cpubkey`, @@ -1711,14 +1709,15 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid` WHERE `contact`.`id` = %d AND `user`.`uid` = %d", - dbesc($contact["id"], $importer["uid"]); + dbesc($contact["id"]), dbesc($importer["uid"]) ); if ($r) { + logger("Now import the DFRN feed"); dfrn2::import($xml,$r[0], true); return; } } -*/ + // Test - remove before flight //if ($pass < 2) { // $tempfile = tempnam(get_temppath(), "dfrn-consume-"); From e2a8146307123f638d035d6c34bde16c96444e88 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 5 Feb 2016 09:03:17 +0100 Subject: [PATCH 039/273] Added some to-do points --- include/import-dfrn.php | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/include/import-dfrn.php b/include/import-dfrn.php index 72d507bbf1..244018887b 100644 --- a/include/import-dfrn.php +++ b/include/import-dfrn.php @@ -829,6 +829,26 @@ class dfrn2 { return; } } +/* + if(activity_match($item['verb'],ACTIVITY_FOLLOW)) { + logger('consume-feed: New follower'); + new_follower($importer,$contact,$item); + return; + } + if(activity_match($item['verb'],ACTIVITY_UNFOLLOW)) { + lose_follower($importer,$contact,$item); + return; + } + if(activity_match($item['verb'],ACTIVITY_REQ_FRIEND)) { + logger('consume-feed: New friend request'); + new_follower($importer,$contact,$item,(?),true); + return; + } + if(activity_match($item['verb'],ACTIVITY_UNFRIEND)) { + lose_sharer($importer,$contact,$item); + return; + } +*/ } $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", @@ -1125,11 +1145,11 @@ class dfrn2 { return; if($importer["readonly"]) { - // We aren't receiving stuff from this person. But we will quietly ignore them - // rather than a blatant "go away" message. - logger('ignoring contact '.$importer["id"]); - return; - } + // We aren't receiving stuff from this person. But we will quietly ignore them + // rather than a blatant "go away" message. + logger('ignoring contact '.$importer["id"]); + return; + } $doc = new DOMDocument(); @$doc->loadXML($xml); @@ -1163,6 +1183,7 @@ class dfrn2 { // is it a public forum? Private forums aren't supported by now with this method $forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue); + /// @todo Check the opposite as well (forum changed to non-forum) if ($forum) q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d", intval($forum), intval($forum), From a81d929cdf223ac0ecf5407a3696db788cd705c4 Mon Sep 17 00:00:00 2001 From: Andrej Stieben <e-mail@stieben.de> Date: Fri, 5 Feb 2016 14:11:10 +0100 Subject: [PATCH 040/273] Minor documentation update, as requested by @tobiasd --- doc/de/Plugins.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/de/Plugins.md b/doc/de/Plugins.md index 11113843bb..40be4a0695 100644 --- a/doc/de/Plugins.md +++ b/doc/de/Plugins.md @@ -5,7 +5,8 @@ Friendica Addon/Plugin-Entwicklung Bitte schau dir das Beispiel-Addon "randplace" für ein funktionierendes Beispiel für manche der hier aufgeführten Funktionen an. Das Facebook-Addon bietet ein Beispiel dafür, die "addon"- und "module"-Funktion gemeinsam zu integrieren. -Addons arbeiten, indem sie Event Hooks abfangen. Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden +Addons arbeiten, indem sie Event Hooks abfangen. +Module arbeiten, indem bestimmte Seitenanfragen (durch den URL-Pfad) abgefangen werden. Plugin-Namen können keine Leerstellen oder andere Interpunktionen enthalten und werden als Datei- und Funktionsnamen genutzt. Du kannst einen lesbaren Namen im Kommentarblock eintragen. @@ -18,7 +19,7 @@ Plugins sollten einen Kommentarblock mit den folgenden vier Parametern enthalten /* * Name: My Great Plugin - * Description: This is what my plugin does. It's really cool + * Description: This is what my plugin does. It's really cool. * Version: 1.0 * Author: John Q. Public <john@myfriendicasite.com> */ @@ -59,9 +60,9 @@ Module Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen. Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss. -Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://my.web.site/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. +Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://example.com/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden. -So würde http://my.web.site/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst: +So würde http://example.com/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst: $a->argc = 3 $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); From f70f4bb89937d3056f71d8a2d5ba32836946186e Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Fri, 5 Feb 2016 18:43:46 +0100 Subject: [PATCH 041/273] ACL: fix TypeError when selecting permissions --- js/acl.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/js/acl.js b/js/acl.js index 8e4e06c83c..3c96d13469 100644 --- a/js/acl.js +++ b/js/acl.js @@ -28,7 +28,7 @@ function ACL(backend_url, preset, automention, is_mobile){ if (preset.length==0) this.showall.addClass("selected"); /*events*/ - this.showall.click(this.on_showall); + this.showall.click(this.on_showall.bind(this)); $(document).on("click", ".acl-button-show", this.on_button_show.bind(this)); $(document).on("click", ".acl-button-hide", this.on_button_hide.bind(this)); $("#acl-search").keypress(this.on_search.bind(this)); @@ -123,12 +123,8 @@ ACL.prototype.on_button_show = function(event){ event.preventDefault() event.stopImmediatePropagation() event.stopPropagation(); - - /*this.showall.removeClass("selected"); - $(this).siblings(".acl-button-hide").removeClass("selected"); - $(this).toggleClass("selected");*/ - - this.set_allow($(this).parent().attr('id')); + + this.set_allow($(event.target).parent().attr('id')); return false; } @@ -137,11 +133,7 @@ ACL.prototype.on_button_hide = function(event){ event.stopImmediatePropagation() event.stopPropagation(); - /*this.showall.removeClass("selected"); - $(this).siblings(".acl-button-show").removeClass("selected"); - $(this).toggleClass("selected");*/ - - this.set_deny($(this).parent().attr('id')); + this.set_deny($(event.target).parent().attr('id')); return false; } @@ -303,7 +295,7 @@ ACL.prototype.populate = function(data){ html = "<div class='acl-list-item {4} {5} type{2}' title='{6}' id='{2}{3}'>"+this.item_tpl+"</div>"; html = html.format(item.photo, item.name, item.type, item.id, (item.forum=='1'?'forum':''), item.network, item.link); if (item.uids!=undefined) this.group_uids[item.id] = item.uids; - //console.log(html); + this.list_content.append(html); this.data[item.id] = item; }.bind(this)); From 41405ed0f2ff24dff89e11e5ecd695fb4cf11066 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Fri, 5 Feb 2016 21:24:01 +0100 Subject: [PATCH 042/273] vier: display smiley-preview as inline-block - needs addons update --- view/theme/vier/dark.css | 5 +++++ view/theme/vier/style.css | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/view/theme/vier/dark.css b/view/theme/vier/dark.css index 8e128ae27f..7c671e4b7d 100644 --- a/view/theme/vier/dark.css +++ b/view/theme/vier/dark.css @@ -63,3 +63,8 @@ li :hover { #viewcontact_wrapper-network { background-color: #343434; } + +table.smiley-preview{ + background-color: #252C33 !important; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.7); +} \ No newline at end of file diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index 0b52b50960..2b78d25d7f 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -2024,6 +2024,15 @@ section.minimal { cursor: pointer; margin-top: 3px; height: 10px; + display: inline-block; +} +#smileybutton { + position: absolute; + z-index: 99; +} +table.smiley-preview{ + background-color: #FFF; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.7); } #jot-perms-icon { float: right; From d408cea871e00a7f3c5e58b466395802eba523f7 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 5 Feb 2016 21:25:20 +0100 Subject: [PATCH 043/273] DFRN import has now gone live --- include/delivery.php | 2 +- include/items.php | 2145 +----------------------------------------- mod/dfrn_notify.php | 3 +- 3 files changed, 7 insertions(+), 2143 deletions(-) diff --git a/include/delivery.php b/include/delivery.php index 5ef942dd06..021ceb9968 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -374,7 +374,7 @@ function delivery_run(&$argv, &$argc){ break; logger('mod-delivery: local delivery'); - local_delivery($x[0],$atom); + dfrn::import($atom, $x[0]); break; } } diff --git a/include/items.php b/include/items.php index 52a1071f2d..798ee56958 100644 --- a/include/items.php +++ b/include/items.php @@ -17,7 +17,7 @@ require_once('include/feed.php'); require_once('include/Contact.php'); require_once('mod/share.php'); require_once('include/enotify.php'); -require_once('include/import-dfrn.php'); +require_once('include/dfrn.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); @@ -145,413 +145,6 @@ function title_is_body($title, $body) { return($title == $body); } -function get_atom_elements($feed, $item, $contact = array()) { - - require_once('library/HTMLPurifier.auto.php'); - require_once('include/html2bbcode.php'); - - $best_photo = array(); - - $res = array(); - - $author = $item->get_author(); - if($author) { - $res['author-name'] = unxmlify($author->get_name()); - $res['author-link'] = unxmlify($author->get_link()); - } - else { - $res['author-name'] = unxmlify($feed->get_title()); - $res['author-link'] = unxmlify($feed->get_permalink()); - } - $res['uri'] = unxmlify($item->get_id()); - $res['title'] = unxmlify($item->get_title()); - $res['body'] = unxmlify($item->get_content()); - $res['plink'] = unxmlify($item->get_link(0)); - - if($res['plink']) - $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3)); - else - $base_url = ''; - - // look for a photo. We should check media size and find the best one, - // but for now let's just find any author photo - // Additionally we look for an alternate author link. On OStatus this one is the one we want. - - $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"]; - if (is_array($authorlinks)) { - foreach ($authorlinks as $link) { - $linkdata = array_shift($link["attribs"]); - - if ($linkdata["rel"] == "alternate") - $res["author-link"] = $linkdata["href"]; - }; - } - - $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); - - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate') - $res['author-link'] = unxmlify($link['attribs']['']['href']); - - if(!x($res, 'author-avatar') || !$res['author-avatar']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - - $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor'); - - if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) { - $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - if($base && count($base)) { - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link'])) - $res['author-link'] = unxmlify($link['attribs']['']['href']); - if(!x($res, 'author-avatar') || !$res['author-avatar']) { - if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - } - - // No photo/profile-link on the item - look at the feed level - - if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) { - $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link'])) - $res['author-link'] = unxmlify($link['attribs']['']['href']); - if(! $res['author-avatar']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - - $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject'); - - if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) { - $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - - if($base && count($base)) { - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link'])) - $res['author-link'] = unxmlify($link['attribs']['']['href']); - if(! (x($res,'author-avatar'))) { - if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - } - } - - $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info'); - if($apps && $apps[0]['attribs']['']['source']) { - $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source'])); - if($res['app'] === 'web') - $res['app'] = 'OStatus'; - } - - // base64 encoded json structure representing Diaspora signature - - $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature'); - if($dsig) { - $res['dsprsig'] = unxmlify($dsig[0]['data']); - } - - $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid'); - if($dguid) - $res['guid'] = unxmlify($dguid[0]['data']); - - $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark'); - if($bm) - $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0); - - - /** - * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it. - */ - - $have_real_body = false; - - $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env'); - if($rawenv) { - $have_real_body = true; - $res['body'] = $rawenv[0]['data']; - $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']); - // make sure nobody is trying to sneak some html tags by us - $res['body'] = notags(base64url_decode($res['body'])); - } - - - $res['body'] = limit_body_size($res['body']); - - // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust - // the content type. Our own network only emits text normally, though it might have been converted to - // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will - // have to assume it is all html and needs to be purified. - - // It doesn't matter all that much security wise - because before this content is used anywhere, we are - // going to escape any tags we find regardless, but this lets us import a limited subset of html from - // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining - // html. - - if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) { - - $res['body'] = reltoabs($res['body'],$base_url); - - $res['body'] = html2bb_video($res['body']); - - $res['body'] = oembed_html2bbcode($res['body']); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - // we shouldn't need a whitelist, because the bbcode converter - // will strip out any unsupported tags. - - $purifier = new HTMLPurifier($config); - $res['body'] = $purifier->purify($res['body']); - - $res['body'] = @html2bbcode($res['body']); - - - } - elseif(! $have_real_body) { - - // it's not one of our messages and it has no tags - // so it's probably just text. We'll escape it just to be safe. - - $res['body'] = escape_tags($res['body']); - } - - - // this tag is obsolete but we keep it for really old sites - - $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow'); - if($allow && $allow[0]['data'] == 1) - $res['last-child'] = 1; - else - $res['last-child'] = 0; - - $private = $item->get_item_tags(NAMESPACE_DFRN,'private'); - if($private && intval($private[0]['data']) > 0) - $res['private'] = intval($private[0]['data']); - else - $res['private'] = 0; - - $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid'); - if($extid && $extid[0]['data']) - $res['extid'] = $extid[0]['data']; - - $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location'); - if($rawlocation) - $res['location'] = unxmlify($rawlocation[0]['data']); - - - $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published'); - if($rawcreated) - $res['created'] = unxmlify($rawcreated[0]['data']); - - - $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated'); - if($rawedited) - $res['edited'] = unxmlify($rawedited[0]['data']); - - if((x($res,'edited')) && (! (x($res,'created')))) - $res['created'] = $res['edited']; - - if(! $res['created']) - $res['created'] = $item->get_date('c'); - - if(! $res['edited']) - $res['edited'] = $item->get_date('c'); - - - // Disallow time travelling posts - - $d1 = strtotime($res['created']); - $d2 = strtotime($res['edited']); - $d3 = strtotime('now'); - - if($d1 > $d3) - $res['created'] = datetime_convert(); - if($d2 > $d3) - $res['edited'] = datetime_convert(); - - $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner'); - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']) - $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']); - elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']) - $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']); - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']) - $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']); - elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']) - $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']); - - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - - foreach($base as $link) { - if(!x($res, 'owner-avatar') || !$res['owner-avatar']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $res['owner-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - - $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point'); - if($rawgeo) - $res['coord'] = unxmlify($rawgeo[0]['data']); - - if ($contact["network"] == NETWORK_FEED) { - $res['verb'] = ACTIVITY_POST; - $res['object-type'] = ACTIVITY_OBJ_NOTE; - } - - $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb'); - - // select between supported verbs - - if($rawverb) { - $res['verb'] = unxmlify($rawverb[0]['data']); - } - - // translate OStatus unfollow to activity streams if it happened to get selected - - if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow')) - $res['verb'] = ACTIVITY_UNFOLLOW; - - $cats = $item->get_categories(); - if($cats) { - $tag_arr = array(); - foreach($cats as $cat) { - $term = $cat->get_term(); - if(! $term) - $term = $cat->get_label(); - $scheme = $cat->get_scheme(); - if($scheme && $term && stristr($scheme,'X-DFRN:')) - $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]'; - elseif($term) - $tag_arr[] = notags(trim($term)); - } - $res['tag'] = implode(',', $tag_arr); - } - - $attach = $item->get_enclosures(); - if($attach) { - $att_arr = array(); - foreach($attach as $att) { - $len = intval($att->get_length()); - $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link())))); - $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title())))); - $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type())))); - if(strpos($type,';')) - $type = substr($type,0,strpos($type,';')); - if((! $link) || (strpos($link,'http') !== 0)) - continue; - - if(! $title) - $title = ' '; - if(! $type) - $type = 'application/octet-stream'; - - $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]'; - } - $res['attach'] = implode(',', $att_arr); - } - - $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object'); - - if($rawobj) { - $res['object'] = '<object>' . "\n"; - $child = $rawobj[0]['child']; - if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) { - $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data']; - $res['object'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n"; - } - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data']) - $res['object'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) - $res['object'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data']) - $res['object'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) { - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']; - if(! $body) - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data']; - // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events - $res['object'] .= '<orig>' . xmlify($body) . '</orig>' . "\n"; - if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - - $body = html2bb_video($body); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - $purifier = new HTMLPurifier($config); - $body = $purifier->purify($body); - $body = html2bbcode($body); - } - - $res['object'] .= '<content>' . $body . '</content>' . "\n"; - } - - $res['object'] .= '</object>' . "\n"; - } - - $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target'); - - if($rawobj) { - $res['target'] = '<target>' . "\n"; - $child = $rawobj[0]['child']; - if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) { - $res['target'] .= '<type>' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '</type>' . "\n"; - } - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data']) - $res['target'] .= '<id>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '</id>' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) - $res['target'] .= '<link>' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '</link>' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data']) - $res['target'] .= '<title>' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '</title>' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) { - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']; - if(! $body) - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data']; - // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events - $res['target'] .= '<orig>' . xmlify($body) . '</orig>' . "\n"; - if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - - $body = html2bb_video($body); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - $purifier = new HTMLPurifier($config); - $body = $purifier->purify($body); - $body = html2bbcode($body); - } - - $res['target'] .= '<content>' . $body . '</content>' . "\n"; - } - - $res['target'] .= '</target>' . "\n"; - } - - $arr = array('feed' => $feed, 'item' => $item, 'result' => $res); - - call_hooks('parse_atom', $arr); - - return $res; -} - function add_page_info_data($data) { call_hooks('page_info_data', $data); @@ -698,27 +291,6 @@ function add_page_info_to_body($body, $texturl = false, $no_photos = false) { return $body; } -function encode_rel_links($links) { - $o = ''; - if(! ((is_array($links)) && (count($links)))) - return $o; - foreach($links as $link) { - $o .= '<link '; - if($link['attribs']['']['rel']) - $o .= 'rel="' . $link['attribs']['']['rel'] . '" '; - if($link['attribs']['']['type']) - $o .= 'type="' . $link['attribs']['']['type'] . '" '; - if($link['attribs']['']['href']) - $o .= 'href="' . $link['attribs']['']['href'] . '" '; - if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width']) - $o .= 'media:width="' . $link['attribs'][NAMESPACE_MEDIA]['width'] . '" '; - if( (x($link['attribs'],NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height']) - $o .= 'media:height="' . $link['attribs'][NAMESPACE_MEDIA]['height'] . '" '; - $o .= ' />' . "\n" ; - } - return xmlify($o); -} - function add_guid($item) { $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"])); if ($r) @@ -1713,652 +1285,10 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) ); if ($r) { logger("Now import the DFRN feed"); - dfrn2::import($xml,$r[0], true); + dfrn::import($xml,$r[0], true); return; } } - - // Test - remove before flight - //if ($pass < 2) { - // $tempfile = tempnam(get_temppath(), "dfrn-consume-"); - // file_put_contents($tempfile, $xml); - //} - - require_once('library/simplepie/simplepie.inc'); - require_once('include/contact_selectors.php'); - - if(! strlen($xml)) { - logger('consume_feed: empty input'); - return; - } - - $feed = new SimplePie(); - $feed->set_raw_data($xml); - if($datedir) - $feed->enable_order_by_date(true); - else - $feed->enable_order_by_date(false); - $feed->init(); - - if($feed->error()) - logger('consume_feed: Error parsing XML: ' . $feed->error()); - - $permalink = $feed->get_permalink(); - - // Check at the feed level for updated contact name and/or photo - - $name_updated = ''; - $new_name = ''; - $photo_timestamp = ''; - $photo_url = ''; - $birthday = ''; - $contact_updated = ''; - - $hubs = $feed->get_links('hub'); - logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA); - - if(count($hubs)) - $hub = implode(',', $hubs); - - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); - if(! $rawtags) - $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { - $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; - $new_name = $elems['name'][0]['data']; - - // Manually checking for changed contact names - if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) { - $name_updated = date("c"); - $photo_timestamp = date("c"); - } - } - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) { - if ($photo_timestamp == "") - $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); - $photo_url = $elems['link'][0]['attribs']['']['href']; - } - - if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) { - $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']); - } - } - - if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) { - logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']); - - $contact_updated = $photo_timestamp; - - require_once("include/Photo.php"); - $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']); - - q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s' - WHERE `uid` = %d AND `id` = %d AND NOT `self`", - dbesc(datetime_convert()), - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - intval($contact['uid']), - intval($contact['id']) - ); - } - - if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) { - if ($name_updated > $contact_updated) - $contact_updated = $name_updated; - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1", - intval($contact['uid']), - intval($contact['id']) - ); - - $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`", - dbesc(notags(trim($new_name))), - dbesc(datetime_convert()), - intval($contact['uid']), - intval($contact['id']), - dbesc(notags(trim($new_name))) - ); - - // do our best to update the name on content items - - if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) { - q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'", - dbesc(notags(trim($new_name))), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - intval($contact['uid']), - dbesc(notags(trim($new_name))) - ); - } - } - - if ($contact_updated AND $new_name AND $photo_url) - poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']); - - if(strlen($birthday)) { - if(substr($birthday,0,4) != $contact['bdyear']) { - logger('consume_feed: updating birthday: ' . $birthday); - - /** - * - * Add new birthday event for this person - * - * $bdtext is just a readable placeholder in case the event is shared - * with others. We will replace it during presentation to our $importer - * to contain a sparkle link and perhaps a photo. - * - */ - - $bdtext = sprintf( t('%s\'s birthday'), $contact['name']); - $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]' ) ; - - - $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", - intval($contact['uid']), - intval($contact['id']), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert('UTC','UTC', $birthday)), - dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')), - dbesc($bdtext), - dbesc($bdtext2), - dbesc('birthday') - ); - - - // update bdyear - - q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d", - dbesc(substr($birthday,0,4)), - intval($contact['uid']), - intval($contact['id']) - ); - - // This function is called twice without reloading the contact - // Make sure we only create one event. This is why &$contact - // is a reference var in this function - - $contact['bdyear'] = substr($birthday,0,4); - } - } - - $community_page = 0; - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community'); - if($rawtags) { - $community_page = intval($rawtags[0]['data']); - } - if(is_array($contact) && intval($contact['forum']) != $community_page) { - q("update contact set forum = %d where id = %d", - intval($community_page), - intval($contact['id']) - ); - $contact['forum'] = (string) $community_page; - } - - - // process any deleted entries - - $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); - if(is_array($del_entries) && count($del_entries) && $pass != 2) { - foreach($del_entries as $dentry) { - $deleted = false; - if(isset($dentry['attribs']['']['ref'])) { - $uri = $dentry['attribs']['']['ref']; - $deleted = true; - if(isset($dentry['attribs']['']['when'])) { - $when = $dentry['attribs']['']['when']; - $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); - } - else - $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); - } - if($deleted && is_array($contact)) { - $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` - WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", - dbesc($uri), - intval($importer['uid']), - intval($contact['id']) - ); - if(count($r)) { - $item = $r[0]; - - if(! $item['deleted']) - logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); - - if($item['object-type'] === ACTIVITY_OBJ_EVENT) { - logger("Deleting event ".$item['event-id'], LOGGER_DEBUG); - event_delete($item['event-id']); - } - - if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item['object'],false); - $xt = parse_xml_string($item['target'],false); - if($xt->type === ACTIVITY_OBJ_NOTE) { - $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(count($i)) { - - // For tags, the owner cannot remove the tag on the author's copy of the post. - - $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); - $author_remove = (($item['origin'] && $item['self']) ? true : false); - $author_copy = (($item['origin']) ? true : false); - - if($owner_remove && $author_copy) - continue; - if($author_remove || $owner_remove) { - $tags = explode(',',$i[0]['tag']); - $newtags = array(); - if(count($tags)) { - foreach($tags as $tag) - if(trim($tag) !== trim($xo->body)) - $newtags[] = trim($tag); - } - q("update item set tag = '%s' where id = %d", - dbesc(implode(',',$newtags)), - intval($i[0]['id']) - ); - create_tags_from_item($i[0]['id']); - } - } - } - } - - if($item['uri'] == $item['parent-uri']) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($item['uri']), - intval($importer['uid']) - ); - create_tags_from_itemuri($item['uri'], $importer['uid']); - create_files_from_itemuri($item['uri'], $importer['uid']); - update_thread_uri($item['uri'], $importer['uid']); - } - else { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($uri), - intval($importer['uid']) - ); - create_tags_from_itemuri($uri, $importer['uid']); - create_files_from_itemuri($uri, $importer['uid']); - if($item['last-child']) { - // ensure that last-child is set in case the comment that had it just got wiped. - q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", - dbesc(datetime_convert()), - dbesc($item['parent-uri']), - intval($item['uid']) - ); - // who is the last child now? - $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d - ORDER BY `created` DESC LIMIT 1", - dbesc($item['parent-uri']), - intval($importer['uid']) - ); - if(count($r)) { - q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", - intval($r[0]['id']) - ); - } - } - } - } - } - } - } - - // Now process the feed - - if($feed->get_item_quantity()) { - - logger('consume_feed: feed item count = ' . $feed->get_item_quantity()); - - // in inverse date order - if ($datedir) - $items = array_reverse($feed->get_items()); - else - $items = $feed->get_items(); - - - foreach($items as $item) { - - $is_reply = false; - $item_id = $item->get_id(); - $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to'); - if(isset($rawthread[0]['attribs']['']['ref'])) { - $is_reply = true; - $parent_uri = $rawthread[0]['attribs']['']['ref']; - } - - if(($is_reply) && is_array($contact)) { - - if($pass == 1) - continue; - - // not allowed to post - - if($contact['rel'] == CONTACT_IS_FOLLOWER) - continue; - - - // Have we seen it? If not, import it. - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed, $item, $contact); - - if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-name'] = $contact['name']; - if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-link'] = $contact['url']; - if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-avatar'] = $contact['thumb']; - - if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) { - logger('consume_feed: no author information! ' . print_r($datarray,true)); - continue; - } - - $force_parent = false; - if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) { - if($contact['network'] === NETWORK_OSTATUS) - $force_parent = true; - if(strlen($datarray['title'])) - unset($datarray['title']); - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($parent_uri), - intval($importer['uid']) - ); - $datarray['last-child'] = 1; - update_thread_uri($parent_uri, $importer['uid']); - } - - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - create_tags_from_itemuri($item_id, $importer['uid']); - update_thread_uri($item_id, $importer['uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) { - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($parent_uri), - intval($importer['uid']) - ); - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - update_thread_uri($item_id, $importer['uid']); - } - continue; - } - - - if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) { - // one way feed - no remote comment ability - $datarray['last-child'] = 0; - } - $datarray['parent-uri'] = $parent_uri; - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - if(($datarray['verb'] === ACTIVITY_LIKE) - || ($datarray['verb'] === ACTIVITY_DISLIKE) - || ($datarray['verb'] === ACTIVITY_ATTEND) - || ($datarray['verb'] === ACTIVITY_ATTENDNO) - || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($r)) - continue; - - // extract tag, if not duplicate, add to parent item - if($xo->id && $xo->content) { - $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; - if(! (stristr($r[0]['tag'],$newtag))) { - q("UPDATE item SET tag = '%s' WHERE id = %d", - dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), - intval($r[0]['id']) - ); - create_tags_from_item($r[0]['id']); - } - } - } - } - - $r = item_store($datarray,$force_parent); - continue; - } - - else { - - // Head post of a conversation. Have we seen it? If not, import it. - - $item_id = $item->get_id(); - - $datarray = get_atom_elements($feed, $item, $contact); - - if(is_array($contact)) { - if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-name'] = $contact['name']; - if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-link'] = $contact['url']; - if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-avatar'] = $contact['thumb']; - } - - if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) { - logger('consume_feed: no author information! ' . print_r($datarray,true)); - continue; - } - - // special handling for events - - if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { - $ev = bbtoevent($datarray['body']); - if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item_id; - $ev['edited'] = $datarray['edited']; - $ev['private'] = $datarray['private']; - $ev['guid'] = $datarray['guid']; - - if(is_array($contact)) - $ev['cid'] = $contact['id']; - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - if(count($r)) - $ev['id'] = $r[0]['id']; - $xyz = event_store($ev); - continue; - } - } - - if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) { - if(strlen($datarray['title'])) - unset($datarray['title']); - $datarray['last-child'] = 1; - } - - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - create_tags_from_itemuri($item_id, $importer['uid']); - update_thread_uri($item_id, $importer['uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if($allow && $allow[0]['data'] != $r[0]['last-child']) { - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - update_thread_uri($item_id, $importer['uid']); - } - continue; - } - - if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) { - logger('consume-feed: New follower'); - new_follower($importer,$contact,$datarray,$item); - return; - } - if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) { - lose_follower($importer,$contact,$datarray,$item); - return; - } - - if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) { - logger('consume-feed: New friend request'); - new_follower($importer,$contact,$datarray,$item,true); - return; - } - if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) { - lose_sharer($importer,$contact,$datarray,$item); - return; - } - - - if(! is_array($contact)) - return; - - - if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) { - // one way feed - no remote comment ability - $datarray['last-child'] = 0; - } - if($contact['network'] === NETWORK_FEED) - $datarray['private'] = 2; - - $datarray['parent-uri'] = $item_id; - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - - if(! link_compare($datarray['owner-link'],$contact['url'])) { - // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - // but otherwise there's a possible data mixup on the sender's system. - // the tgroup delivery code called from item_store will correct it if it's a forum, - // but we're going to unconditionally correct it here so that the post will always be owned by our contact. - logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - $datarray['owner-avatar'] = $contact['thumb']; - } - - // We've allowed "followers" to reach this point so we can decide if they are - // posting an @-tag delivery, which followers are allowed to do for certain - // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it. - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray))) - continue; - - // This is my contact on another system, but it's really me. - // Turn this into a wall post. - $notify = item_is_remote_self($contact, $datarray); - - $r = item_store($datarray, false, $notify); - logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG); - continue; - - } - } - } } function item_is_remote_self($contact, &$datarray) { @@ -2421,1073 +1351,6 @@ function item_is_remote_self($contact, &$datarray) { return true; } -function local_delivery($importer,$data) { - // dfrn-Test - return dfrn2::import($data, $importer); - - require_once('library/simplepie/simplepie.inc'); - - $a = get_app(); - - logger(__function__, LOGGER_TRACE); - - //$tempfile = tempnam(get_temppath(), "dfrn-local-"); - //file_put_contents($tempfile, $data); - - if($importer['readonly']) { - // We aren't receiving stuff from this person. But we will quietly ignore them - // rather than a blatant "go away" message. - logger('local_delivery: ignoring'); - return 0; - //NOTREACHED - } - - // Consume notification feed. This may differ from consuming a public feed in several ways - // - might contain email or friend suggestions - // - might contain remote followup to our message - // - in which case we need to accept it and then notify other conversants - // - we may need to send various email notifications - - $feed = new SimplePie(); - $feed->set_raw_data($data); - $feed->enable_order_by_date(false); - $feed->init(); - - - if($feed->error()) - logger('local_delivery: Error parsing XML: ' . $feed->error()); - - - // Check at the feed level for updated contact name and/or photo - - $name_updated = ''; - $new_name = ''; - $photo_timestamp = ''; - $photo_url = ''; - $contact_updated = ''; - - - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); - -// Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags -// if(! $rawtags) -// $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { - $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; - $new_name = $elems['name'][0]['data']; - - // Manually checking for changed contact names - if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) { - $name_updated = date("c"); - $photo_timestamp = date("c"); - } - } - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) { - if ($photo_timestamp == "") - $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); - $photo_url = $elems['link'][0]['attribs']['']['href']; - } - } - - if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) { - - $contact_updated = $photo_timestamp; - - logger('local_delivery: Updating photo for ' . $importer['name']); - require_once("include/Photo.php"); - - $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']); - - q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s' - WHERE `uid` = %d AND `id` = %d AND NOT `self`", - dbesc(datetime_convert()), - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - intval($importer['importer_uid']), - intval($importer['id']) - ); - } - - if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) { - if ($name_updated > $contact_updated) - $contact_updated = $name_updated; - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1", - intval($importer['importer_uid']), - intval($importer['id']) - ); - - $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`", - dbesc(notags(trim($new_name))), - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($importer['id']), - dbesc(notags(trim($new_name))) - ); - - // do our best to update the name on content items - - if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) { - q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'", - dbesc(notags(trim($new_name))), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - intval($importer['importer_uid']), - dbesc(notags(trim($new_name))) - ); - } - } - - if ($contact_updated AND $new_name AND $photo_url) - poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']); - - // Currently unsupported - needs a lot of work - $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' ); - if(isset($reloc[0]['child'][NAMESPACE_DFRN])) { - $base = $reloc[0]['child'][NAMESPACE_DFRN]; - $newloc = array(); - $newloc['uid'] = $importer['importer_uid']; - $newloc['cid'] = $importer['id']; - $newloc['name'] = notags(unxmlify($base['name'][0]['data'])); - $newloc['photo'] = notags(unxmlify($base['photo'][0]['data'])); - $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data'])); - $newloc['micro'] = notags(unxmlify($base['micro'][0]['data'])); - $newloc['url'] = notags(unxmlify($base['url'][0]['data'])); - $newloc['request'] = notags(unxmlify($base['request'][0]['data'])); - $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data'])); - $newloc['notify'] = notags(unxmlify($base['notify'][0]['data'])); - $newloc['poll'] = notags(unxmlify($base['poll'][0]['data'])); - $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data'])); - /** relocated user must have original key pair */ - /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data'])); - $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/ - - logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG); - - // update contact - $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;", - intval($importer['id']), - intval($importer['importer_uid'])); - if ($r === false) - return 1; - $old = $r[0]; - - $x = q("UPDATE contact SET - name = '%s', - photo = '%s', - thumb = '%s', - micro = '%s', - url = '%s', - nurl = '%s', - request = '%s', - confirm = '%s', - notify = '%s', - poll = '%s', - `site-pubkey` = '%s' - WHERE id=%d AND uid=%d;", - dbesc($newloc['name']), - dbesc($newloc['photo']), - dbesc($newloc['thumb']), - dbesc($newloc['micro']), - dbesc($newloc['url']), - dbesc(normalise_link($newloc['url'])), - dbesc($newloc['request']), - dbesc($newloc['confirm']), - dbesc($newloc['notify']), - dbesc($newloc['poll']), - dbesc($newloc['sitepubkey']), - intval($importer['id']), - intval($importer['importer_uid'])); - - if ($x === false) - return 1; - // update items - $fields = array( - 'owner-link' => array($old['url'], $newloc['url']), - 'author-link' => array($old['url'], $newloc['url']), - 'owner-avatar' => array($old['photo'], $newloc['photo']), - 'author-avatar' => array($old['photo'], $newloc['photo']), - ); - foreach ($fields as $n=>$f){ - $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d", - $n, dbesc($f[1]), - $n, dbesc($f[0]), - intval($importer['importer_uid'])); - if ($x === false) - return 1; - } - - /// @TODO - /// merge with current record, current contents have priority - /// update record, set url-updated - /// update profile photos - /// schedule a scan? - return 0; - } - - - // handle friend suggestion notification - - $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' ); - if(isset($sugg[0]['child'][NAMESPACE_DFRN])) { - $base = $sugg[0]['child'][NAMESPACE_DFRN]; - $fsugg = array(); - $fsugg['uid'] = $importer['importer_uid']; - $fsugg['cid'] = $importer['id']; - $fsugg['name'] = notags(unxmlify($base['name'][0]['data'])); - $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data'])); - $fsugg['url'] = notags(unxmlify($base['url'][0]['data'])); - $fsugg['request'] = notags(unxmlify($base['request'][0]['data'])); - $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data'])); - - // Does our member already have a friend matching this description? - - $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc($fsugg['name']), - dbesc(normalise_link($fsugg['url'])), - intval($fsugg['uid']) - ); - if(count($r)) - return 0; - - // Do we already have an fcontact record for this person? - - $fid = 0; - $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($fsugg['url']), - dbesc($fsugg['name']), - dbesc($fsugg['request']) - ); - if(count($r)) { - $fid = $r[0]['id']; - - // OK, we do. Do we already have an introduction for this person ? - $r = q("select id from intro where uid = %d and fid = %d limit 1", - intval($fsugg['uid']), - intval($fid) - ); - if(count($r)) - return 0; - } - if(! $fid) - $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ", - dbesc($fsugg['name']), - dbesc($fsugg['url']), - dbesc($fsugg['photo']), - dbesc($fsugg['request']) - ); - $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($fsugg['url']), - dbesc($fsugg['name']), - dbesc($fsugg['request']) - ); - if(count($r)) { - $fid = $r[0]['id']; - } - // database record did not get created. Quietly give up. - else - return 0; - - - $hash = random_string(); - - $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` ) - VALUES( %d, %d, %d, '%s', '%s', '%s', %d )", - intval($fsugg['uid']), - intval($fid), - intval($fsugg['cid']), - dbesc($fsugg['body']), - dbesc($hash), - dbesc(datetime_convert()), - intval(0) - ); - - notification(array( - 'type' => NOTIFY_SUGGEST, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $fsugg, - 'link' => $a->get_baseurl() . '/notifications/intros', - 'source_name' => $importer['name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['photo'], - 'verb' => ACTIVITY_REQ_FRIEND, - 'otype' => 'intro' - )); - - return 0; - } - - $ismail = false; - - $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' ); - if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) { - - logger('local_delivery: private message received'); - - $ismail = true; - $base = $rawmail[0]['child'][NAMESPACE_DFRN]; - - $msg = array(); - $msg['uid'] = $importer['importer_uid']; - $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data'])); - $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])); - $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])); - $msg['contact-id'] = $importer['id']; - $msg['title'] = notags(unxmlify($base['subject'][0]['data'])); - $msg['body'] = escape_tags(unxmlify($base['content'][0]['data'])); - $msg['seen'] = 0; - $msg['replied'] = 0; - $msg['uri'] = notags(unxmlify($base['id'][0]['data'])); - $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data'])); - $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data']))); - - dbesc_array($msg); - - $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) - . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" ); - - // send notifications. - - require_once('include/enotify.php'); - - $notif_params = array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $msg, - 'source_name' => $msg['from-name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' - ); - - notification($notif_params); - return 0; - - // NOTREACHED - } - - $community_page = 0; - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community'); - if($rawtags) { - $community_page = intval($rawtags[0]['data']); - } - if(intval($importer['forum']) != $community_page) { - q("update contact set forum = %d where id = %d", - intval($community_page), - intval($importer['id']) - ); - $importer['forum'] = (string) $community_page; - } - - logger('local_delivery: feed item count = ' . $feed->get_item_quantity()); - - // process any deleted entries - - $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); - if(is_array($del_entries) && count($del_entries)) { - foreach($del_entries as $dentry) { - $deleted = false; - if(isset($dentry['attribs']['']['ref'])) { - $uri = $dentry['attribs']['']['ref']; - $deleted = true; - if(isset($dentry['attribs']['']['when'])) { - $when = $dentry['attribs']['']['when']; - $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); - } - else - $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); - } - if($deleted) { - - // check for relayed deletes to our conversation - - $is_reply = false; - $r = q("select * from item where uri = '%s' and uid = %d limit 1", - dbesc($uri), - intval($importer['importer_uid']) - ); - if(count($r)) { - $parent_uri = $r[0]['parent-uri']; - if($r[0]['id'] != $r[0]['parent']) - $is_reply = true; - } - - if($is_reply) { - $community = false; - - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) { - $sql_extra = ''; - $community = true; - logger('local_delivery: possible community delete'); - } - else - $sql_extra = " and contact.self = 1 and item.wall = 1 "; - - // was the top-level post for this reply written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_delete = false; - - // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? - $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, - `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` - INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($parent_uri), - dbesc($parent_uri), - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - if($r && count($r)) - $is_a_remote_delete = true; - - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_delete && $community) { - if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) { - $is_a_remote_delete = false; - logger('local_delivery: not a community delete'); - } - } - - if($is_a_remote_delete) { - logger('local_delivery: received remote delete'); - } - } - - $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id` - WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", - dbesc($uri), - intval($importer['importer_uid']), - intval($importer['id']) - ); - - if(count($r)) { - $item = $r[0]; - - if($item['deleted']) - continue; - - logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); - - if($item['object-type'] === ACTIVITY_OBJ_EVENT) { - logger("Deleting event ".$item['event-id'], LOGGER_DEBUG); - event_delete($item['event-id']); - } - - if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item['object'],false); - $xt = parse_xml_string($item['target'],false); - - if($xt->type === ACTIVITY_OBJ_NOTE) { - $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(count($i)) { - - // For tags, the owner cannot remove the tag on the author's copy of the post. - - $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); - $author_remove = (($item['origin'] && $item['self']) ? true : false); - $author_copy = (($item['origin']) ? true : false); - - if($owner_remove && $author_copy) - continue; - if($author_remove || $owner_remove) { - $tags = explode(',',$i[0]['tag']); - $newtags = array(); - if(count($tags)) { - foreach($tags as $tag) - if(trim($tag) !== trim($xo->body)) - $newtags[] = trim($tag); - } - q("update item set tag = '%s' where id = %d", - dbesc(implode(',',$newtags)), - intval($i[0]['id']) - ); - create_tags_from_item($i[0]['id']); - } - } - } - } - - if($item['uri'] == $item['parent-uri']) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($item['uri']), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item['uri'], $importer['importer_uid']); - create_files_from_itemuri($item['uri'], $importer['importer_uid']); - update_thread_uri($item['uri'], $importer['importer_uid']); - } - else { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($uri), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($uri, $importer['importer_uid']); - create_files_from_itemuri($uri, $importer['importer_uid']); - update_thread_uri($uri, $importer['importer_uid']); - if($item['last-child']) { - // ensure that last-child is set in case the comment that had it just got wiped. - q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", - dbesc(datetime_convert()), - dbesc($item['parent-uri']), - intval($item['uid']) - ); - // who is the last child now? - $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d - ORDER BY `created` DESC LIMIT 1", - dbesc($item['parent-uri']), - intval($importer['importer_uid']) - ); - if(count($r)) { - q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", - intval($r[0]['id']) - ); - } - } - // if this is a relayed delete, propagate it to other recipients - - if($is_a_remote_delete) - proc_run('php',"include/notifier.php","drop",$item['id']); - } - } - } - } - } - - - foreach($feed->get_items() as $item) { - - $is_reply = false; - $item_id = $item->get_id(); - $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to'); - if(isset($rawthread[0]['attribs']['']['ref'])) { - $is_reply = true; - $parent_uri = $rawthread[0]['attribs']['']['ref']; - } - - if($is_reply) { - $community = false; - - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) { - $sql_extra = ''; - $community = true; - logger('local_delivery: possible community reply'); - } - else - $sql_extra = " and contact.self = 1 and item.wall = 1 "; - - // was the top-level post for this reply written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_comment = false; - $top_uri = $parent_uri; - - $r = q("select `item`.`parent-uri` from `item` - WHERE `item`.`uri` = '%s' - LIMIT 1", - dbesc($parent_uri) - ); - if($r && count($r)) { - $top_uri = $r[0]['parent-uri']; - - // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? - $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, - `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` - INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($top_uri), - dbesc($top_uri), - dbesc($top_uri), - intval($importer['importer_uid']) - ); - if($r && count($r)) - $is_a_remote_comment = true; - } - - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_comment && $community) { - if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) { - $is_a_remote_comment = false; - logger('local_delivery: not a community reply'); - } - } - - if($is_a_remote_comment) { - logger('local_delivery: received remote comment'); - $is_like = false; - // remote reply to our post. Import and then notify everybody else. - - $datarray = get_atom_elements($feed, $item); - - $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - $iid = $r[0]['id']; - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - logger('received updated comment' , LOGGER_DEBUG); - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item_id, $importer['importer_uid']); - - proc_run('php',"include/notifier.php","comment-import",$iid); - - } - - continue; - } - - - - $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1", - intval($importer['importer_uid']) - ); - - - $datarray['type'] = 'remote-comment'; - $datarray['wall'] = 1; - $datarray['parent-uri'] = $parent_uri; - $datarray['uid'] = $importer['importer_uid']; - $datarray['owner-name'] = $own[0]['name']; - $datarray['owner-link'] = $own[0]['url']; - $datarray['owner-avatar'] = $own[0]['thumb']; - $datarray['contact-id'] = $importer['id']; - - if(($datarray['verb'] === ACTIVITY_LIKE) - || ($datarray['verb'] === ACTIVITY_DISLIKE) - || ($datarray['verb'] === ACTIVITY_ATTEND) - || ($datarray['verb'] === ACTIVITY_ATTENDNO) - || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { - $is_like = true; - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - $datarray['last-child'] = 0; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - - ); - if($r && count($r)) - continue; - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) { - - // fetch the parent item - - $tagp = q("select * from item where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($tagp)) - continue; - - // extract tag, if not duplicate, and this user allows tags, add to parent item - - if($xo->id && $xo->content) { - $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; - if(! (stristr($tagp[0]['tag'],$newtag))) { - $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1", - intval($importer['importer_uid']) - ); - if(count($i) && ! intval($i[0]['blocktags'])) { - q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d", - dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag), - intval($tagp[0]['id']), - dbesc(datetime_convert()), - dbesc(datetime_convert()) - ); - create_tags_from_item($tagp[0]['id']); - } - } - } - } - } - - - $posted_id = item_store($datarray); - $parent = 0; - - if($posted_id) { - - $datarray["id"] = $posted_id; - - $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($posted_id), - intval($importer['importer_uid']) - ); - if(count($r)) { - $parent = $r[0]['parent']; - $parent_uri = $r[0]['parent-uri']; - } - - if(! $is_like) { - $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($r[0]['parent']) - ); - - $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($posted_id) - ); - } - - if($posted_id && $parent) { - proc_run('php',"include/notifier.php","comment-import","$posted_id"); - } - - return 0; - // NOTREACHED - } - } - else { - - // regular comment that is part of this total conversation. Have we seen it? If not, import it. - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed,$item); - - if($importer['rel'] == CONTACT_IS_FOLLOWER) - continue; - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item_id, $importer['importer_uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) { - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - } - continue; - } - - $datarray['parent-uri'] = $parent_uri; - $datarray['uid'] = $importer['importer_uid']; - $datarray['contact-id'] = $importer['id']; - if(($datarray['verb'] === ACTIVITY_LIKE) - || ($datarray['verb'] === ACTIVITY_DISLIKE) - || ($datarray['verb'] === ACTIVITY_ATTEND) - || ($datarray['verb'] === ACTIVITY_ATTENDNO) - || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($r)) - continue; - - // extract tag, if not duplicate, add to parent item - if($xo->content) { - if(! (stristr($r[0]['tag'],trim($xo->content)))) { - q("UPDATE item SET tag = '%s' WHERE id = %d", - dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), - intval($r[0]['id']) - ); - create_tags_from_item($r[0]['id']); - } - } - } - } - - $posted_id = item_store($datarray); - - continue; - } - } - - else { - - // Head post of a conversation. Have we seen it? If not, import it. - - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed,$item); - - if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { - $ev = bbtoevent($datarray['body']); - if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { - $ev['cid'] = $importer['id']; - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item_id; - $ev['edited'] = $datarray['edited']; - $ev['private'] = $datarray['private']; - $ev['guid'] = $datarray['guid']; - - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - if(count($r)) - $ev['id'] = $r[0]['id']; - $xyz = event_store($ev); - continue; - } - } - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item_id, $importer['importer_uid']); - update_thread_uri($item_id, $importer['importer_uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if($allow && $allow[0]['data'] != $r[0]['last-child']) { - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - } - continue; - } - - $datarray['parent-uri'] = $item_id; - $datarray['uid'] = $importer['importer_uid']; - $datarray['contact-id'] = $importer['id']; - - - if(! link_compare($datarray['owner-link'],$importer['url'])) { - // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - // but otherwise there's a possible data mixup on the sender's system. - // the tgroup delivery code called from item_store will correct it if it's a forum, - // but we're going to unconditionally correct it here so that the post will always be owned by our contact. - logger('local_delivery: Correcting item owner.', LOGGER_DEBUG); - $datarray['owner-name'] = $importer['senderName']; - $datarray['owner-link'] = $importer['url']; - $datarray['owner-avatar'] = $importer['thumb']; - } - - if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray))) - continue; - - // This is my contact on another system, but it's really me. - // Turn this into a wall post. - $notify = item_is_remote_self($importer, $datarray); - - $posted_id = item_store($datarray, false, $notify); - - if(stristr($datarray['verb'],ACTIVITY_POKE)) { - $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1)); - if(! $verb) - continue; - $xo = parse_xml_string($datarray['object'],false); - - if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { - - // somebody was poked/prodded. Was it me? - - $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); - - foreach($links->link as $l) { - $atts = $l->attributes(); - switch($atts['rel']) { - case "alternate": - $Blink = $atts['href']; - break; - default: - break; - } - } - if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) { - - // send a notification - require_once('include/enotify.php'); - - notification(array( - 'type' => NOTIFY_POKE, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)), - 'source_name' => stripslashes($datarray['author-name']), - 'source_link' => $datarray['author-link'], - 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) - ? $importer['thumb'] : $datarray['author-avatar']), - 'verb' => $datarray['verb'], - 'otype' => 'person', - 'activity' => $verb, - 'parent' => $datarray['parent'] - )); - } - } - } - - continue; - } - } - - return 0; - // NOTREACHED - -} - - function new_follower($importer,$contact,$datarray,$item,$sharing = false) { $url = notags(trim($datarray['author-link'])); $name = notags(trim($datarray['author-name'])); @@ -3598,7 +1461,7 @@ function new_follower($importer,$contact,$datarray,$item,$sharing = false) { } } -function lose_follower($importer,$contact,$datarray,$item) { +function lose_follower($importer,$contact,$datarray = array(),$item = "") { if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) { q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d", @@ -3611,7 +1474,7 @@ function lose_follower($importer,$contact,$datarray,$item) { } } -function lose_sharer($importer,$contact,$datarray,$item) { +function lose_sharer($importer,$contact,$datarray = array(),$item = "") { if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) { q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d", diff --git a/mod/dfrn_notify.php b/mod/dfrn_notify.php index 4aa777b550..780fb456f5 100644 --- a/mod/dfrn_notify.php +++ b/mod/dfrn_notify.php @@ -1,6 +1,7 @@ <?php require_once('include/items.php'); +require_once('include/dfrn.php'); require_once('include/event.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); @@ -208,7 +209,7 @@ function dfrn_notify_post(&$a) { logger('rino: decrypted data: ' . $data, LOGGER_DATA); } - $ret = local_delivery($importer,$data); + $ret = dfrn::import($data, $importer); xml_status($ret); // NOTREACHED From 9fd3d8116bee7ff6ede2477f6a1b5ca163257fc7 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Fri, 5 Feb 2016 21:30:31 +0100 Subject: [PATCH 044/273] vier smileybutton: little polish in dark.css --- view/theme/vier/dark.css | 1 - 1 file changed, 1 deletion(-) diff --git a/view/theme/vier/dark.css b/view/theme/vier/dark.css index 7c671e4b7d..5ddaf71a2f 100644 --- a/view/theme/vier/dark.css +++ b/view/theme/vier/dark.css @@ -66,5 +66,4 @@ li :hover { table.smiley-preview{ background-color: #252C33 !important; - box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.7); } \ No newline at end of file From 1c82e9f209eb72a7ed8d0fc1ee6a66b8d02d4110 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 5 Feb 2016 21:31:11 +0100 Subject: [PATCH 045/273] Bugfix for OStatus to prevent sending messages from wrong senders --- include/dfrn.php | 1259 ++++++++++++++++++++++++++++++++++++++- include/import-dfrn.php | 1229 -------------------------------------- include/ostatus.php | 4 + 3 files changed, 1260 insertions(+), 1232 deletions(-) delete mode 100644 include/import-dfrn.php diff --git a/include/dfrn.php b/include/dfrn.php index 4c1f21dd06..e286b75cce 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -6,9 +6,19 @@ * https://github.com/friendica/friendica/wiki/Protocol */ -require_once('include/items.php'); -require_once('include/Contact.php'); -require_once('include/ostatus.php'); +require_once("include/Contact.php"); +require_once("include/ostatus.php"); +require_once("include/enotify.php"); +require_once("include/threads.php"); +require_once("include/socgraph.php"); +require_once("include/items.php"); +require_once("include/tags.php"); +require_once("include/files.php"); +require_once("include/event.php"); +require_once("include/text.php"); +require_once("include/oembed.php"); +require_once("include/html2bbcode.php"); +require_once("library/HTMLPurifier.auto.php"); /** * @brief This class contain functions to create and send DFRN XML files @@ -16,6 +26,10 @@ require_once('include/ostatus.php'); */ class dfrn { + const DFRN_TOP_LEVEL = 0; + const DFRN_REPLY = 1; + const DFRN_REPLY_RC = 2; + /** * @brief Generates the atom entries for delivery.php * @@ -1053,4 +1067,1243 @@ class dfrn { return $res->status; } + + /** + * @brief Add new birthday event for this person + * + * @param array $contact Contact record + * @param string $birthday Birthday of the contact + * + */ + private function birthday_event($contact, $birthday) { + + logger("updating birthday: ".$birthday." for contact ".$contact["id"]); + + $bdtext = sprintf(t("%s\'s birthday"), $contact["name"]); + $bdtext2 = sprintf(t("Happy Birthday %s"), " [url=".$contact["url"]."]".$contact["name"]."[/url]") ; + + + $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", + intval($contact["uid"]), + intval($contact["id"]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert("UTC","UTC", $birthday)), + dbesc(datetime_convert("UTC","UTC", $birthday." + 1 day ")), + dbesc($bdtext), + dbesc($bdtext2), + dbesc("birthday") + ); + } + + /** + * @brief Fetch the author data from head or entry items + * + * @param object $xpath XPath object + * @param object $context In which context should the data be searched + * @param array $importer Record of the importer user mixed with contact of the content + * @param string $element Element name from which the data is fetched + * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well + * + * @return Returns an array with relevant data of the author + */ + private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch) { + + $author = array(); + $author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue; + $author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue; + + $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, + `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd` + FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); + if ($r) { + $contact = $r[0]; + $author["contact-id"] = $r[0]["id"]; + $author["network"] = $r[0]["network"]; + } else { + $author["contact-id"] = $importer["id"]; + $author["network"] = $importer["network"]; + $onlyfetch = true; + } + + // Until now we aren't serving different sizes - but maybe later + $avatarlist = array(); + // @todo check if "avatar" or "photo" would be the best field in the specification + $avatars = $xpath->query($element."/atom:link[@rel='avatar']", $context); + foreach($avatars AS $avatar) { + $href = ""; + $width = 0; + foreach($avatar->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "width") + $width = $attributes->textContent; + if ($attributes->name == "updated") + $contact["avatar-date"] = $attributes->textContent; + } + if (($width > 0) AND ($href != "")) + $avatarlist[$width] = $href; + } + if (count($avatarlist) > 0) { + krsort($avatarlist); + $author["avatar"] = current($avatarlist); + } + + if ($r AND !$onlyfetch) { + + // When was the last change to name or uri? + $name_element = $xpath->query($element."/atom:name", $context)->item(0); + foreach($name_element->attributes AS $attributes) + if ($attributes->name == "updated") + $contact["name-date"] = $attributes->textContent; + + $link_element = $xpath->query($element."/atom:link", $context)->item(0); + foreach($link_element->attributes AS $attributes) + if ($attributes->name == "updated") + $contact["uri-date"] = $attributes->textContent; + + // Update contact data + $value = $xpath->evaluate($element."/dfrn:handle/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["addr"] = $value; + + $value = $xpath->evaluate($element."/poco:displayName/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; + + $value = $xpath->evaluate($element."/poco:preferredUsername/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; + + $value = $xpath->evaluate($element."/poco:note/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["about"] = $value; + + $value = $xpath->evaluate($element."/poco:address/poco:formatted/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; + + /// @todo Add support for the following fields that we don't support by now in the contact table: + /// - poco:utcOffset + /// - poco:ims + /// - poco:urls + /// - poco:locality + /// - poco:region + /// - poco:country + + // Save the keywords into the contact table + $tags = array(); + $tagelements = $xpath->evaluate($element."/poco:tags/text()", $context); + foreach($tagelements AS $tag) + $tags[$tag->nodeValue] = $tag->nodeValue; + + if (count($tags)) + $contact["keywords"] = implode(", ", $tags); + + // "dfrn:birthday" contains the birthday converted to UTC + $old_bdyear = $contact["bdyear"]; + + $birthday = $xpath->evaluate($element."/dfrn:birthday/text()", $context)->item(0)->nodeValue; + + if (strtotime($birthday) > time()) { + $bd_timestamp = strtotime($birthday); + + $contact["bdyear"] = date("Y", $bd_timestamp); + } + + // "poco:birthday" is the birthday in the format "yyyy-mm-dd" + $value = $xpath->evaluate($element."/poco:birthday/text()", $context)->item(0)->nodeValue; + + if (!in_array($value, array("", "0000-00-00"))) { + $bdyear = date("Y"); + $value = str_replace("0000", $bdyear, $value); + + if (strtotime($value) < time()) { + $value = str_replace($bdyear, $bdyear + 1, $value); + $bdyear = $bdyear + 1; + } + + $contact["bd"] = $value; + } + + if ($old_bdyear != $contact["bdyear"]) + self::birthday_event($contact, $birthday); + + // Get all field names + $fields = array(); + foreach ($r[0] AS $field => $data) + $fields[$field] = $data; + + unset($fields["id"]); + unset($fields["uid"]); + unset($fields["avatar-date"]); + unset($fields["name-date"]); + unset($fields["uri-date"]); + + // Update check for this field has to be done differently + $datefields = array("name-date", "uri-date"); + foreach ($datefields AS $field) + if (strtotime($contact[$field]) > strtotime($r[0][$field])) + $update = true; + + foreach ($fields AS $field => $data) + if ($contact[$field] != $r[0][$field]) { + logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG); + $update = true; + } + + if ($update) { + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); + + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', + `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s', + `name-date` = '%s', `uri-date` = '%s' + WHERE `id` = %d AND `network` = '%s'", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), + dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]), + dbesc($contact["bd"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]), + intval($contact["id"]), dbesc($contact["network"])); + } + + update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"], + (strtotime($contact["avatar-date"]) > strtotime($r[0]["avatar-date"]))); + + $contact["generation"] = 2; + $contact["photo"] = $author["avatar"]; + update_gcontact($contact); + } + + return($author); + } + + private function transform_activity($xpath, $activity, $element) { + if (!is_object($activity)) + return ""; + + $obj_doc = new DOMDocument("1.0", "utf-8"); + $obj_doc->formatOutput = true; + + $obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element); + + $activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue; + xml_add_element($obj_doc, $obj_element, "type", $activity_type); + + $id = $xpath->query("atom:id", $activity)->item(0); + if (is_object($id)) + $obj_element->appendChild($obj_doc->importNode($id, true)); + + $title = $xpath->query("atom:title", $activity)->item(0); + if (is_object($title)) + $obj_element->appendChild($obj_doc->importNode($title, true)); + + $link = $xpath->query("atom:link", $activity)->item(0); + if (is_object($link)) + $obj_element->appendChild($obj_doc->importNode($link, true)); + + $content = $xpath->query("atom:content", $activity)->item(0); + if (is_object($content)) + $obj_element->appendChild($obj_doc->importNode($content, true)); + + $obj_doc->appendChild($obj_element); + + $objxml = $obj_doc->saveXML($obj_element); + + // @todo This isn't totally clean. We should find a way to transform the namespaces + $objxml = str_replace("<".$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml); + return($objxml); + } + + private function process_mail($xpath, $mail, $importer) { + + logger("Processing mails"); + + $msg = array(); + $msg["uid"] = $importer["importer_uid"]; + $msg["from-name"] = $xpath->query("dfrn:sender/dfrn:name/text()", $mail)->item(0)->nodeValue; + $msg["from-url"] = $xpath->query("dfrn:sender/dfrn:uri/text()", $mail)->item(0)->nodeValue; + $msg["from-photo"] = $xpath->query("dfrn:sender/dfrn:avatar/text()", $mail)->item(0)->nodeValue; + $msg["contact-id"] = $importer["id"]; + $msg["uri"] = $xpath->query("dfrn:id/text()", $mail)->item(0)->nodeValue; + $msg["parent-uri"] = $xpath->query("dfrn:in-reply-to/text()", $mail)->item(0)->nodeValue; + $msg["created"] = $xpath->query("dfrn:sentdate/text()", $mail)->item(0)->nodeValue; + $msg["title"] = $xpath->query("dfrn:subject/text()", $mail)->item(0)->nodeValue; + $msg["body"] = $xpath->query("dfrn:content/text()", $mail)->item(0)->nodeValue; + $msg["seen"] = 0; + $msg["replied"] = 0; + + dbesc_array($msg); + + $r = dbq("INSERT INTO `mail` (`".implode("`, `", array_keys($msg))."`) VALUES ('".implode("', '", array_values($msg))."')"); + + // send notifications. + + $notif_params = array( + "type" => NOTIFY_MAIL, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $msg, + "source_name" => $msg["from-name"], + "source_link" => $importer["url"], + "source_photo" => $importer["thumb"], + "verb" => ACTIVITY_POST, + "otype" => "mail" + ); + + notification($notif_params); + + logger("Mail is processed, notification was sent."); + } + + private function process_suggestion($xpath, $suggestion, $importer) { + + logger("Processing suggestions"); + + $suggest = array(); + $suggest["uid"] = $importer["importer_uid"]; + $suggest["cid"] = $importer["id"]; + $suggest["url"] = $xpath->query("dfrn:url/text()", $suggestion)->item(0)->nodeValue; + $suggest["name"] = $xpath->query("dfrn:name/text()", $suggestion)->item(0)->nodeValue; + $suggest["photo"] = $xpath->query("dfrn:photo/text()", $suggestion)->item(0)->nodeValue; + $suggest["request"] = $xpath->query("dfrn:request/text()", $suggestion)->item(0)->nodeValue; + $suggest["body"] = $xpath->query("dfrn:note/text()", $suggestion)->item(0)->nodeValue; + + // Does our member already have a friend matching this description? + + $r = q("SELECT `id` FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc($suggest["name"]), + dbesc(normalise_link($suggest["url"])), + intval($suggest["uid"]) + ); + if(count($r)) + return false; + + // Do we already have an fcontact record for this person? + + $fid = 0; + $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($suggest["url"]), + dbesc($suggest["name"]), + dbesc($suggest["request"]) + ); + if(count($r)) { + $fid = $r[0]["id"]; + + // OK, we do. Do we already have an introduction for this person ? + $r = q("SELECT `id` FROM `intro` WHERE `uid` = %d AND `fid` = %d LIMIT 1", + intval($suggest["uid"]), + intval($fid) + ); + if(count($r)) + return false; + } + if(!$fid) + $r = q("INSERT INTO `fcontact` (`name`,`url`,`photo`,`request`) VALUES ('%s', '%s', '%s', '%s')", + dbesc($suggest["name"]), + dbesc($suggest["url"]), + dbesc($suggest["photo"]), + dbesc($suggest["request"]) + ); + $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($suggest["url"]), + dbesc($suggest["name"]), + dbesc($suggest["request"]) + ); + if(count($r)) + $fid = $r[0]["id"]; + else + // database record did not get created. Quietly give up. + return false; + + + $hash = random_string(); + + $r = q("INSERT INTO `intro` (`uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked`) + VALUES(%d, %d, %d, '%s', '%s', '%s', %d)", + intval($suggest["uid"]), + intval($fid), + intval($suggest["cid"]), + dbesc($suggest["body"]), + dbesc($hash), + dbesc(datetime_convert()), + intval(0) + ); + + notification(array( + "type" => NOTIFY_SUGGEST, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $suggest, + "link" => App::get_baseurl()."/notifications/intros", + "source_name" => $importer["name"], + "source_link" => $importer["url"], + "source_photo" => $importer["photo"], + "verb" => ACTIVITY_REQ_FRIEND, + "otype" => "intro" + )); + + return true; + + } + + private function process_relocation($xpath, $relocation, $importer) { + + logger("Processing relocations"); + + $relocate = array(); + $relocate["uid"] = $importer["importer_uid"]; + $relocate["cid"] = $importer["id"]; + $relocate["url"] = $xpath->query("dfrn:url/text()", $relocation)->item(0)->nodeValue; + $relocate["name"] = $xpath->query("dfrn:name/text()", $relocation)->item(0)->nodeValue; + $relocate["photo"] = $xpath->query("dfrn:photo/text()", $relocation)->item(0)->nodeValue; + $relocate["thumb"] = $xpath->query("dfrn:thumb/text()", $relocation)->item(0)->nodeValue; + $relocate["micro"] = $xpath->query("dfrn:micro/text()", $relocation)->item(0)->nodeValue; + $relocate["request"] = $xpath->query("dfrn:request/text()", $relocation)->item(0)->nodeValue; + $relocate["confirm"] = $xpath->query("dfrn:confirm/text()", $relocation)->item(0)->nodeValue; + $relocate["notify"] = $xpath->query("dfrn:notify/text()", $relocation)->item(0)->nodeValue; + $relocate["poll"] = $xpath->query("dfrn:poll/text()", $relocation)->item(0)->nodeValue; + $relocate["sitepubkey"] = $xpath->query("dfrn:sitepubkey/text()", $relocation)->item(0)->nodeValue; + + // update contact + $r = q("SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d;", + intval($importer["id"]), + intval($importer["importer_uid"])); + if (!$r) + return false; + + $old = $r[0]; + + $x = q("UPDATE `contact` SET + `name` = '%s', + `photo` = '%s', + `thumb` = '%s', + `micro` = '%s', + `url` = '%s', + `nurl` = '%s', + `request` = '%s', + `confirm` = '%s', + `notify` = '%s', + `poll` = '%s', + `site-pubkey` = '%s' + WHERE `id` = %d AND `uid` = %d;", + dbesc($relocate["name"]), + dbesc($relocate["photo"]), + dbesc($relocate["thumb"]), + dbesc($relocate["micro"]), + dbesc($relocate["url"]), + dbesc(normalise_link($relocate["url"])), + dbesc($relocate["request"]), + dbesc($relocate["confirm"]), + dbesc($relocate["notify"]), + dbesc($relocate["poll"]), + dbesc($relocate["sitepubkey"]), + intval($importer["id"]), + intval($importer["importer_uid"])); + + if ($x === false) + return false; + + // update items + $fields = array( + 'owner-link' => array($old["url"], $relocate["url"]), + 'author-link' => array($old["url"], $relocate["url"]), + 'owner-avatar' => array($old["photo"], $relocate["photo"]), + 'author-avatar' => array($old["photo"], $relocate["photo"]), + ); + foreach ($fields as $n=>$f){ + $x = q("UPDATE `item` SET `%s` = '%s' WHERE `%s` = '%s' AND `uid` = %d", + $n, dbesc($f[1]), + $n, dbesc($f[0]), + intval($importer["importer_uid"])); + if ($x === false) + return false; + } + + /// @TODO + /// merge with current record, current contents have priority + /// update record, set url-updated + /// update profile photos + /// schedule a scan? + return true; + } + + private function update_content($current, $item, $importer, $entrytype) { + $changed = false; + + if (edited_timestamp_is_newer($current, $item)) { + + // do not accept (ignore) an earlier edit than one we currently have. + if(datetime_convert("UTC","UTC",$item["edited"]) < $current["edited"]) + return(false); + + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + dbesc($item["title"]), + dbesc($item["body"]), + dbesc($item["tag"]), + dbesc(datetime_convert("UTC","UTC",$item["edited"])), + dbesc(datetime_convert()), + dbesc($item["uri"]), + intval($importer["importer_uid"]) + ); + create_tags_from_itemuri($item["uri"], $importer["importer_uid"]); + update_thread_uri($item["uri"], $importer["importer_uid"]); + + $changed = true; + + if ($entrytype == DFRN_REPLY_RC) + proc_run("php", "include/notifier.php","comment-import", $current["id"]); + } + + // update last-child if it changes + if($item["last-child"] AND ($item["last-child"] != $current["last-child"])) { + $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc(datetime_convert()), + dbesc($item["parent-uri"]), + intval($importer["importer_uid"]) + ); + $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + intval($item["last-child"]), + dbesc(datetime_convert()), + dbesc($item["uri"]), + intval($importer["importer_uid"]) + ); + } + return $changed; + } + + private function get_entry_type($importer, $item) { + if ($item["parent-uri"] != $item["uri"]) { + $community = false; + + if($importer["page-flags"] == PAGE_COMMUNITY || $importer["page-flags"] == PAGE_PRVGROUP) { + $sql_extra = ""; + $community = true; + logger("possible community action"); + } else + $sql_extra = " AND `contact`.`self` AND `item`.`wall` "; + + // was the top-level post for this action written by somebody on this site? + // Specifically, the recipient? + + $is_a_remote_action = false; + + $r = q("SELECT `item`.`parent-uri` FROM `item` + WHERE `item`.`uri` = '%s' + LIMIT 1", + dbesc($item["parent-uri"]) + ); + if($r && count($r)) { + $r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item` + INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' OR `item`.`thr-parent` = '%s') + AND `item`.`uid` = %d + $sql_extra + LIMIT 1", + dbesc($r[0]["parent-uri"]), + dbesc($r[0]["parent-uri"]), + dbesc($r[0]["parent-uri"]), + intval($importer["importer_uid"]) + ); + if($r && count($r)) + $is_a_remote_action = true; + } + + // Does this have the characteristics of a community or private group action? + // If it's an action to a wall post on a community/prvgroup page it's a + // valid community action. Also forum_mode makes it valid for sure. + // If neither, it's not. + + if($is_a_remote_action && $community) { + if((!$r[0]["forum_mode"]) && (!$r[0]["wall"])) { + $is_a_remote_action = false; + logger("not a community action"); + } + } + + if ($is_a_remote_action) + return DFRN_REPLY_RC; + else + return DFRN_REPLY; + + } else + return DFRN_TOP_LEVEL; + + } + + private function do_poke($item, $importer, $posted_id) { + $verb = urldecode(substr($item["verb"],strpos($item["verb"], "#")+1)); + if(!$verb) + return; + $xo = parse_xml_string($item["object"],false); + + if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { + + // somebody was poked/prodded. Was it me? + $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); + + foreach($links->link as $l) { + $atts = $l->attributes(); + switch($atts["rel"]) { + case "alternate": + $Blink = $atts["href"]; + break; + default: + break; + } + } + if($Blink && link_compare($Blink,$a->get_baseurl()."/profile/".$importer["nickname"])) { + + // send a notification + notification(array( + "type" => NOTIFY_POKE, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $item, + "link" => $a->get_baseurl()."/display/".urlencode(get_item_guid($posted_id)), + "source_name" => stripslashes($item["author-name"]), + "source_link" => $item["author-link"], + "source_photo" => ((link_compare($item["author-link"],$importer["url"])) + ? $importer["thumb"] : $item["author-avatar"]), + "verb" => $item["verb"], + "otype" => "person", + "activity" => $verb, + "parent" => $item["parent"] + )); + } + } + } + + private function process_entry($header, $xpath, $entry, $importer) { + + logger("Processing entries"); + + $item = $header; + + // Get the uri + $item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue; + + // Fetch the owner + $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", true); + + $item["owner-name"] = $owner["name"]; + $item["owner-link"] = $owner["link"]; + $item["owner-avatar"] = $owner["avatar"]; + + // fetch the author + $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", true); + + $item["author-name"] = $author["name"]; + $item["author-link"] = $author["link"]; + $item["author-avatar"] = $author["avatar"]; + + $item["title"] = $xpath->query("atom:title/text()", $entry)->item(0)->nodeValue; + + $item["created"] = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; + $item["edited"] = $xpath->query("atom:updated/text()", $entry)->item(0)->nodeValue; + + $item["body"] = $xpath->query("dfrn:env/text()", $entry)->item(0)->nodeValue; + $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]); + // make sure nobody is trying to sneak some html tags by us + $item["body"] = notags(base64url_decode($item["body"])); + + $item["body"] = limit_body_size($item["body"]); + + /// @todo Do we really need this check for HTML elements? (It was copied from the old function) + if((strpos($item['body'],'<') !== false) && (strpos($item['body'],'>') !== false)) { + + $item['body'] = reltoabs($item['body'],$base_url); + + $item['body'] = html2bb_video($item['body']); + + $item['body'] = oembed_html2bbcode($item['body']); + + $config = HTMLPurifier_Config::createDefault(); + $config->set('Cache.DefinitionImpl', null); + + // we shouldn't need a whitelist, because the bbcode converter + // will strip out any unsupported tags. + + $purifier = new HTMLPurifier($config); + $item['body'] = $purifier->purify($item['body']); + + $item['body'] = @html2bbcode($item['body']); + } + + // We don't need the content element since "dfrn:env" is always present + //$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue; + + $item["last-child"] = $xpath->query("dfrn:comment-allow/text()", $entry)->item(0)->nodeValue; + $item["location"] = $xpath->query("dfrn:location/text()", $entry)->item(0)->nodeValue; + + $georsspoint = $xpath->query("georss:point", $entry); + if ($georsspoint) + $item["coord"] = $georsspoint->item(0)->nodeValue; + + $item["private"] = $xpath->query("dfrn:private/text()", $entry)->item(0)->nodeValue; + + $item["extid"] = $xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue; + + if ($xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue == "true") + $item["bookmark"] = true; + + $notice_info = $xpath->query("statusnet:notice_info", $entry); + if ($notice_info AND ($notice_info->length > 0)) { + foreach($notice_info->item(0)->attributes AS $attributes) { + if ($attributes->name == "source") + $item["app"] = strip_tags($attributes->textContent); + } + } + + $item["guid"] = $xpath->query("dfrn:diaspora_guid/text()", $entry)->item(0)->nodeValue; + + // We store the data from "dfrn:diaspora_signature" in a different table, this is done in "item_store" + $dsprsig = unxmlify($xpath->query("dfrn:diaspora_signature/text()", $entry)->item(0)->nodeValue); + if ($dsprsig != "") + $item["dsprsig"] = $dsprsig; + + $item["verb"] = $xpath->query("activity:verb/text()", $entry)->item(0)->nodeValue; + + if ($xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue != "") + $item["object-type"] = $xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue; + + $object = $xpath->query("activity:object", $entry)->item(0); + $item["object"] = self::transform_activity($xpath, $object, "object"); + + if (trim($item["object"]) != "") { + $r = parse_xml_string($item["object"], false); + if (isset($r->type)) + $item["object-type"] = $r->type; + } + + $target = $xpath->query("activity:target", $entry)->item(0); + $item["target"] = self::transform_activity($xpath, $target, "target"); + + $categories = $xpath->query("atom:category", $entry); + if ($categories) { + foreach ($categories AS $category) { + foreach($category->attributes AS $attributes) + if ($attributes->name == "term") { + $term = $attributes->textContent; + if(strlen($item["tag"])) + $item["tag"] .= ","; + + $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + } + } + } + + $enclosure = ""; + + $links = $xpath->query("atom:link", $entry); + if ($links) { + $rel = ""; + $href = ""; + $type = ""; + $length = "0"; + $title = ""; + foreach ($links AS $link) { + foreach($link->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "rel") + $rel = $attributes->textContent; + if ($attributes->name == "type") + $type = $attributes->textContent; + if ($attributes->name == "length") + $length = $attributes->textContent; + if ($attributes->name == "title") + $title = $attributes->textContent; + } + if (($rel != "") AND ($href != "")) + switch($rel) { + case "alternate": + $item["plink"] = $href; + break; + case "enclosure": + $enclosure = $href; + if(strlen($item["attach"])) + $item["attach"] .= ","; + + $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; + break; + } + } + } + + // Is it a reply or a top level posting? + $item["parent-uri"] = $item["uri"]; + + $inreplyto = $xpath->query("thr:in-reply-to", $entry); + if (is_object($inreplyto->item(0))) + foreach($inreplyto->item(0)->attributes AS $attributes) + if ($attributes->name == "ref") + $item["parent-uri"] = $attributes->textContent; + + // Get the type of the item (Top level post, reply or remote reply) + $entrytype = self::get_entry_type($importer, $item); + + // Now assign the rest of the values that depend on the type of the message + if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { + if (!isset($item["object-type"])) + $item["object-type"] = ACTIVITY_OBJ_COMMENT; + + if ($item["contact-id"] != $owner["contact-id"]) + $item["contact-id"] = $owner["contact-id"]; + + if (($item["network"] != $owner["network"]) AND ($owner["network"] != "")) + $item["network"] = $owner["network"]; + + if ($item["contact-id"] != $author["contact-id"]) + $item["contact-id"] = $author["contact-id"]; + + if (($item["network"] != $author["network"]) AND ($author["network"] != "")) + $item["network"] = $author["network"]; + } + + if ($entrytype == DFRN_REPLY_RC) { + $item["type"] = "remote-comment"; + $item["wall"] = 1; + } elseif ($entrytype == DFRN_TOP_LEVEL) { + if (!isset($item["object-type"])) + $item["object-type"] = ACTIVITY_OBJ_NOTE; + + if ($item["object-type"] == ACTIVITY_OBJ_EVENT) { + logger("Item ".$item["uri"]." seems to contain an event.", LOGGER_DEBUG); + $ev = bbtoevent($item["body"]); + if((x($ev, "desc") || x($ev, "summary")) && x($ev, "start")) { + logger("Event in item ".$item["uri"]." was found.", LOGGER_DEBUG); + $ev["cid"] = $importer["id"]; + $ev["uid"] = $importer["uid"]; + $ev["uri"] = $item["uri"]; + $ev["edited"] = $item["edited"]; + $ev['private'] = $item['private']; + $ev["guid"] = $item["guid"]; + + $r = q("SELECT `id` FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item["uri"]), + intval($importer["uid"]) + ); + if(count($r)) + $ev["id"] = $r[0]["id"]; + + $event_id = event_store($ev); + logger("Event ".$event_id." was stored", LOGGER_DEBUG); + return; + } + } + + // The filling of the the "contact" variable is done for legcy reasons + // The functions below are partly used by ostatus.php as well - where we have this variable + $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"])); + $contact = $r[0]; + $nickname = $contact["nick"]; + + // Big question: Do we need these functions? They were part of the "consume_feed" function. + // This function once was responsible for DFRN and OStatus. + if(activity_match($item['verb'],ACTIVITY_FOLLOW)) { + logger('New follower'); + new_follower($importer, $contact, $item, $nickname); + return; + } + if(activity_match($item['verb'],ACTIVITY_UNFOLLOW)) { + logger('Lost follower'); + lose_follower($importer, $contact, $item); + return; + } + if(activity_match($item['verb'],ACTIVITY_REQ_FRIEND)) { + logger('New friend request'); + new_follower($importer, $contact, $item, $nickname, true); + return; + } + if(activity_match($item['verb'],ACTIVITY_UNFRIEND)) { + logger('Lost sharer'); + lose_sharer($importer, $contact, $item); + return; + } + } + + $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item["uri"]), + intval($importer["importer_uid"]) + ); + + // Update content if 'updated' changes + if(count($r)) { + if (self::update_content($r[0], $item, $importer, $entrytype)) + logger("Item ".$item["uri"]." was updated.", LOGGER_DEBUG); + else + logger("Item ".$item["uri"]." already existed.", LOGGER_DEBUG); + return; + } + + if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { + if($importer["rel"] == CONTACT_IS_FOLLOWER) { + logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); + return; + } + + if(($item["verb"] === ACTIVITY_LIKE) + || ($item["verb"] === ACTIVITY_DISLIKE) + || ($item["verb"] === ACTIVITY_ATTEND) + || ($item["verb"] === ACTIVITY_ATTENDNO) + || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) { + $is_like = true; + $item["type"] = "activity"; + $item["gravity"] = GRAVITY_LIKE; + // only one like or dislike per person + // splitted into two queries for performance issues + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) + ); + if($r && count($r)) + return; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) + ); + if($r && count($r)) + return; + + } else + $is_like = false; + + if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + + $xo = parse_xml_string($item["object"],false); + $xt = parse_xml_string($item["target"],false); + + if($xt->type == ACTIVITY_OBJ_NOTE) { + $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($xt->id), + intval($importer["importer_uid"]) + ); + + if(!count($r)) + return; + + // extract tag, if not duplicate, add to parent item + if($xo->content) { + if(!(stristr($r[0]["tag"],trim($xo->content)))) { + q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", + dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), + intval($r[0]["id"]) + ); + create_tags_from_item($r[0]["id"]); + } + } + } + } + + $posted_id = item_store($item); + $parent = 0; + + if($posted_id) { + + logger("Reply from contact ".$item["contact-id"]." was stored with id ".$posted_id, LOGGER_DEBUG); + + $item["id"] = $posted_id; + + $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($posted_id), + intval($importer["importer_uid"]) + ); + if(count($r)) { + $parent = $r[0]["parent"]; + $parent_uri = $r[0]["parent-uri"]; + } + + if(!$is_like) { + $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", + dbesc(datetime_convert()), + intval($importer["importer_uid"]), + intval($r[0]["parent"]) + ); + + $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", + dbesc(datetime_convert()), + intval($importer["importer_uid"]), + intval($posted_id) + ); + } + + if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) { + logger("Notifying followers about comment ".$posted_id, LOGGER_DEBUG); + proc_run("php", "include/notifier.php", "comment-import", $posted_id); + } + + return true; + } + } else { + if(!link_compare($item["owner-link"],$importer["url"])) { + // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, + // but otherwise there's a possible data mixup on the sender's system. + // the tgroup delivery code called from item_store will correct it if it's a forum, + // but we're going to unconditionally correct it here so that the post will always be owned by our contact. + logger('Correcting item owner.', LOGGER_DEBUG); + $item["owner-name"] = $importer["senderName"]; + $item["owner-link"] = $importer["url"]; + $item["owner-avatar"] = $importer["thumb"]; + } + + if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) { + logger("Contact ".$importer["id"]." is only follower and tgroup check was negative.", LOGGER_DEBUG); + return; + } + + // This is my contact on another system, but it's really me. + // Turn this into a wall post. + $notify = item_is_remote_self($importer, $item); + + $posted_id = item_store($item, false, $notify); + + logger("Item was stored with id ".$posted_id, LOGGER_DEBUG); + + if(stristr($item["verb"],ACTIVITY_POKE)) + self::do_poke($item, $importer, $posted_id); + } + } + + private function process_deletion($header, $xpath, $deletion, $importer) { + + logger("Processing deletions"); + + foreach($deletion->attributes AS $attributes) { + if ($attributes->name == "ref") + $uri = $attributes->textContent; + if ($attributes->name == "when") + $when = $attributes->textContent; + } + if ($when) + $when = datetime_convert("UTC", "UTC", $when, "Y-m-d H:i:s"); + else + $when = datetime_convert("UTC", "UTC", "now", "Y-m-d H:i:s"); + + if (!$uri OR !$importer["id"]) + return false; + + /// @todo Only select the used fields + $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` + WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", + dbesc($uri), + intval($importer["uid"]), + intval($importer["id"]) + ); + if(!count($r)) { + logger("Item with uri ".$uri." from contact ".$importer["id"]." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG); + return; + } else { + + $item = $r[0]; + + $entrytype = self::get_entry_type($importer, $item); + + if(!$item["deleted"]) + logger('deleting item '.$item["id"].' uri='.$uri, LOGGER_DEBUG); + else + return; + + if($item["object-type"] === ACTIVITY_OBJ_EVENT) { + logger("Deleting event ".$item["event-id"], LOGGER_DEBUG); + event_delete($item["event-id"]); + } + + if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($item["object"],false); + $xt = parse_xml_string($item["target"],false); + if($xt->type === ACTIVITY_OBJ_NOTE) { + $i = q("SELECT `id`, `contact-id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($xt->id), + intval($importer["importer_uid"]) + ); + if(count($i)) { + + // For tags, the owner cannot remove the tag on the author's copy of the post. + + $owner_remove = (($item["contact-id"] == $i[0]["contact-id"]) ? true: false); + $author_remove = (($item["origin"] && $item["self"]) ? true : false); + $author_copy = (($item["origin"]) ? true : false); + + if($owner_remove && $author_copy) + return; + if($author_remove || $owner_remove) { + $tags = explode(',',$i[0]["tag"]); + $newtags = array(); + if(count($tags)) { + foreach($tags as $tag) + if(trim($tag) !== trim($xo->body)) + $newtags[] = trim($tag); + } + q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", + dbesc(implode(',',$newtags)), + intval($i[0]["id"]) + ); + create_tags_from_item($i[0]["id"]); + } + } + } + } + + if($entrytype == DFRN_TOP_LEVEL) { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + `body` = '', `title` = '' + WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc($when), + dbesc(datetime_convert()), + dbesc($uri), + intval($importer["uid"]) + ); + create_tags_from_itemuri($uri, $importer["uid"]); + create_files_from_itemuri($uri, $importer["uid"]); + update_thread_uri($uri, $importer["uid"]); + } else { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + `body` = '', `title` = '' + WHERE `uri` = '%s' AND `uid` = %d", + dbesc($when), + dbesc(datetime_convert()), + dbesc($uri), + intval($importer["uid"]) + ); + create_tags_from_itemuri($uri, $importer["uid"]); + create_files_from_itemuri($uri, $importer["uid"]); + update_thread_uri($uri, $importer["importer_uid"]); + if($item["last-child"]) { + // ensure that last-child is set in case the comment that had it just got wiped. + q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", + dbesc(datetime_convert()), + dbesc($item["parent-uri"]), + intval($item["uid"]) + ); + // who is the last child now? + $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d + ORDER BY `created` DESC LIMIT 1", + dbesc($item["parent-uri"]), + intval($importer["uid"]) + ); + if(count($r)) { + q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", + intval($r[0]["id"]) + ); + } + } + // if this is a relayed delete, propagate it to other recipients + + if($entrytype == DFRN_REPLY_RC) { + logger("Notifying followers about deletion of post ".$item["id"], LOGGER_DEBUG); + proc_run("php", "include/notifier.php","drop", $item["id"]); + } + } + } + } + + /** + * @brief Imports a DFRN message + * + * @param text $xml The DFRN message + * @param array $importer Record of the importer user mixed with contact of the content + * @param bool $sort_by_date Is used when feeds are polled + */ + function import($xml,$importer, $sort_by_date = false) { + + if ($xml == "") + return; + + if($importer["readonly"]) { + // We aren't receiving stuff from this person. But we will quietly ignore them + // rather than a blatant "go away" message. + logger('ignoring contact '.$importer["id"]); + return; + } + + $doc = new DOMDocument(); + @$doc->loadXML($xml); + + $xpath = new DomXPath($doc); + $xpath->registerNamespace("atom", NAMESPACE_ATOM1); + $xpath->registerNamespace("thr", NAMESPACE_THREAD); + $xpath->registerNamespace("at", NAMESPACE_TOMB); + $xpath->registerNamespace("media", NAMESPACE_MEDIA); + $xpath->registerNamespace("dfrn", NAMESPACE_DFRN); + $xpath->registerNamespace("activity", NAMESPACE_ACTIVITY); + $xpath->registerNamespace("georss", NAMESPACE_GEORSS); + $xpath->registerNamespace("poco", NAMESPACE_POCO); + $xpath->registerNamespace("ostatus", NAMESPACE_OSTATUS); + $xpath->registerNamespace("statusnet", NAMESPACE_STATUSNET); + + $header = array(); + $header["uid"] = $importer["uid"]; + $header["network"] = NETWORK_DFRN; + $header["type"] = "remote"; + $header["wall"] = 0; + $header["origin"] = 0; + $header["contact-id"] = $importer["id"]; + + // Update the contact table if the data has changed + // Only the "dfrn:owner" in the head section contains all data + self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false); + + logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG); + + // is it a public forum? Private forums aren't supported by now with this method + $forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue); + + if ($forum != $importer["forum"]) + q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d", + intval($forum), intval($forum), + intval($importer["id"]) + ); + + $mails = $xpath->query("/atom:feed/dfrn:mail"); + foreach ($mails AS $mail) + self::process_mail($xpath, $mail, $importer); + + $suggestions = $xpath->query("/atom:feed/dfrn:suggest"); + foreach ($suggestions AS $suggestion) + self::process_suggestion($xpath, $suggestion, $importer); + + $relocations = $xpath->query("/atom:feed/dfrn:relocate"); + foreach ($relocations AS $relocation) + self::process_relocation($xpath, $relocation, $importer); + + $deletions = $xpath->query("/atom:feed/at:deleted-entry"); + foreach ($deletions AS $deletion) + self::process_deletion($header, $xpath, $deletion, $importer); + + if (!$sort_by_date) { + $entries = $xpath->query("/atom:feed/atom:entry"); + foreach ($entries AS $entry) + self::process_entry($header, $xpath, $entry, $importer); + } else { + $newentries = array(); + $entries = $xpath->query("/atom:feed/atom:entry"); + foreach ($entries AS $entry) { + $created = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; + $newentries[strtotime($created)] = $entry; + } + + // Now sort after the publishing date + ksort($newentries); + + foreach ($newentries AS $entry) + self::process_entry($header, $xpath, $entry, $importer); + } + logger("Import done for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG); + } } +?> diff --git a/include/import-dfrn.php b/include/import-dfrn.php deleted file mode 100644 index 244018887b..0000000000 --- a/include/import-dfrn.php +++ /dev/null @@ -1,1229 +0,0 @@ -<?php -require_once("include/enotify.php"); -require_once("include/threads.php"); -require_once("include/socgraph.php"); -require_once("include/items.php"); -require_once("include/tags.php"); -require_once("include/files.php"); -require_once("include/event.php"); - -class dfrn2 { - - const DFRN_TOP_LEVEL = 0; - const DFRN_REPLY = 1; - const DFRN_REPLY_RC = 2; - - /** - * @brief Add new birthday event for this person - * - * @param array $contact Contact record - * @param string $birthday Birthday of the contact - * - */ - private function birthday_event($contact, $birthday) { - - logger("updating birthday: ".$birthday." for contact ".$contact["id"]); - - $bdtext = sprintf(t("%s\'s birthday"), $contact["name"]); - $bdtext2 = sprintf(t("Happy Birthday %s"), " [url=".$contact["url"]."]".$contact["name"]."[/url]") ; - - - $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", - intval($contact["uid"]), - intval($contact["id"]), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert("UTC","UTC", $birthday)), - dbesc(datetime_convert("UTC","UTC", $birthday." + 1 day ")), - dbesc($bdtext), - dbesc($bdtext2), - dbesc("birthday") - ); - } - - /** - * @brief Fetch the author data from head or entry items - * - * @param object $xpath XPath object - * @param object $context In which context should the data be searched - * @param array $importer Record of the importer user mixed with contact of the content - * @param string $element Element name from which the data is fetched - * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well - * - * @return Returns an array with relevant data of the author - */ - private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch) { - - $author = array(); - $author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue; - $author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue; - - $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, - `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd` - FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", - intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); - if ($r) { - $contact = $r[0]; - $author["contact-id"] = $r[0]["id"]; - $author["network"] = $r[0]["network"]; - } else { - $author["contact-id"] = $importer["id"]; - $author["network"] = $importer["network"]; - $onlyfetch = true; - } - - // Until now we aren't serving different sizes - but maybe later - $avatarlist = array(); - // @todo check if "avatar" or "photo" would be the best field in the specification - $avatars = $xpath->query($element."/atom:link[@rel='avatar']", $context); - foreach($avatars AS $avatar) { - $href = ""; - $width = 0; - foreach($avatar->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "width") - $width = $attributes->textContent; - if ($attributes->name == "updated") - $contact["avatar-date"] = $attributes->textContent; - } - if (($width > 0) AND ($href != "")) - $avatarlist[$width] = $href; - } - if (count($avatarlist) > 0) { - krsort($avatarlist); - $author["avatar"] = current($avatarlist); - } - - if ($r AND !$onlyfetch) { - - // When was the last change to name or uri? - $name_element = $xpath->query($element."/atom:name", $context)->item(0); - foreach($name_element->attributes AS $attributes) - if ($attributes->name == "updated") - $contact["name-date"] = $attributes->textContent; - - $link_element = $xpath->query($element."/atom:link", $context)->item(0); - foreach($link_element->attributes AS $attributes) - if ($attributes->name == "updated") - $contact["uri-date"] = $attributes->textContent; - - // Update contact data - $value = $xpath->evaluate($element."/dfrn:handle/text()", $context)->item(0)->nodeValue; - if ($value != "") - $contact["addr"] = $value; - - $value = $xpath->evaluate($element."/poco:displayName/text()", $context)->item(0)->nodeValue; - if ($value != "") - $contact["name"] = $value; - - $value = $xpath->evaluate($element."/poco:preferredUsername/text()", $context)->item(0)->nodeValue; - if ($value != "") - $contact["nick"] = $value; - - $value = $xpath->evaluate($element."/poco:note/text()", $context)->item(0)->nodeValue; - if ($value != "") - $contact["about"] = $value; - - $value = $xpath->evaluate($element."/poco:address/poco:formatted/text()", $context)->item(0)->nodeValue; - if ($value != "") - $contact["location"] = $value; - - /// @todo Add support for the following fields that we don't support by now in the contact table: - /// - poco:utcOffset - /// - poco:ims - /// - poco:urls - /// - poco:locality - /// - poco:region - /// - poco:country - - // Save the keywords into the contact table - $tags = array(); - $tagelements = $xpath->evaluate($element."/poco:tags/text()", $context); - foreach($tagelements AS $tag) - $tags[$tag->nodeValue] = $tag->nodeValue; - - if (count($tags)) - $contact["keywords"] = implode(", ", $tags); - - // "dfrn:birthday" contains the birthday converted to UTC - $old_bdyear = $contact["bdyear"]; - - $birthday = $xpath->evaluate($element."/dfrn:birthday/text()", $context)->item(0)->nodeValue; - - if (strtotime($birthday) > time()) { - $bd_timestamp = strtotime($birthday); - - $contact["bdyear"] = date("Y", $bd_timestamp); - } - - // "poco:birthday" is the birthday in the format "yyyy-mm-dd" - $value = $xpath->evaluate($element."/poco:birthday/text()", $context)->item(0)->nodeValue; - - if (!in_array($value, array("", "0000-00-00"))) { - $bdyear = date("Y"); - $value = str_replace("0000", $bdyear, $value); - - if (strtotime($value) < time()) { - $value = str_replace($bdyear, $bdyear + 1, $value); - $bdyear = $bdyear + 1; - } - - $contact["bd"] = $value; - } - - if ($old_bdyear != $contact["bdyear"]) - self::birthday_event($contact, $birthday); - - // Get all field names - $fields = array(); - foreach ($r[0] AS $field => $data) - $fields[$field] = $data; - - unset($fields["id"]); - unset($fields["uid"]); - unset($fields["avatar-date"]); - unset($fields["name-date"]); - unset($fields["uri-date"]); - - // Update check for this field has to be done differently - $datefields = array("name-date", "uri-date"); - foreach ($datefields AS $field) - if (strtotime($contact[$field]) > strtotime($r[0][$field])) - $update = true; - - foreach ($fields AS $field => $data) - if ($contact[$field] != $r[0][$field]) { - logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG); - $update = true; - } - - if ($update) { - logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - - q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', - `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s', - `name-date` = '%s', `uri-date` = '%s' - WHERE `id` = %d AND `network` = '%s'", - dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), - dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]), - dbesc($contact["bd"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]), - intval($contact["id"]), dbesc($contact["network"])); - } - - update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"], - (strtotime($contact["avatar-date"]) > strtotime($r[0]["avatar-date"]))); - - $contact["generation"] = 2; - $contact["photo"] = $author["avatar"]; - update_gcontact($contact); - } - - return($author); - } - - private function transform_activity($xpath, $activity, $element) { - if (!is_object($activity)) - return ""; - - $obj_doc = new DOMDocument("1.0", "utf-8"); - $obj_doc->formatOutput = true; - - $obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element); - - $activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue; - xml_add_element($obj_doc, $obj_element, "type", $activity_type); - - $id = $xpath->query("atom:id", $activity)->item(0); - if (is_object($id)) - $obj_element->appendChild($obj_doc->importNode($id, true)); - - $title = $xpath->query("atom:title", $activity)->item(0); - if (is_object($title)) - $obj_element->appendChild($obj_doc->importNode($title, true)); - - $link = $xpath->query("atom:link", $activity)->item(0); - if (is_object($link)) - $obj_element->appendChild($obj_doc->importNode($link, true)); - - $content = $xpath->query("atom:content", $activity)->item(0); - if (is_object($content)) - $obj_element->appendChild($obj_doc->importNode($content, true)); - - $obj_doc->appendChild($obj_element); - - $objxml = $obj_doc->saveXML($obj_element); - - // @todo This isn't totally clean. We should find a way to transform the namespaces - $objxml = str_replace("<".$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml); - return($objxml); - } - - private function process_mail($xpath, $mail, $importer) { - - logger("Processing mails"); - - $msg = array(); - $msg["uid"] = $importer["importer_uid"]; - $msg["from-name"] = $xpath->query("dfrn:sender/dfrn:name/text()", $mail)->item(0)->nodeValue; - $msg["from-url"] = $xpath->query("dfrn:sender/dfrn:uri/text()", $mail)->item(0)->nodeValue; - $msg["from-photo"] = $xpath->query("dfrn:sender/dfrn:avatar/text()", $mail)->item(0)->nodeValue; - $msg["contact-id"] = $importer["id"]; - $msg["uri"] = $xpath->query("dfrn:id/text()", $mail)->item(0)->nodeValue; - $msg["parent-uri"] = $xpath->query("dfrn:in-reply-to/text()", $mail)->item(0)->nodeValue; - $msg["created"] = $xpath->query("dfrn:sentdate/text()", $mail)->item(0)->nodeValue; - $msg["title"] = $xpath->query("dfrn:subject/text()", $mail)->item(0)->nodeValue; - $msg["body"] = $xpath->query("dfrn:content/text()", $mail)->item(0)->nodeValue; - $msg["seen"] = 0; - $msg["replied"] = 0; - - dbesc_array($msg); - - $r = dbq("INSERT INTO `mail` (`".implode("`, `", array_keys($msg))."`) VALUES ('".implode("', '", array_values($msg))."')"); - - // send notifications. - - $notif_params = array( - "type" => NOTIFY_MAIL, - "notify_flags" => $importer["notify-flags"], - "language" => $importer["language"], - "to_name" => $importer["username"], - "to_email" => $importer["email"], - "uid" => $importer["importer_uid"], - "item" => $msg, - "source_name" => $msg["from-name"], - "source_link" => $importer["url"], - "source_photo" => $importer["thumb"], - "verb" => ACTIVITY_POST, - "otype" => "mail" - ); - - notification($notif_params); - - logger("Mail is processed, notification was sent."); - } - - private function process_suggestion($xpath, $suggestion, $importer) { - - logger("Processing suggestions"); - - $suggest = array(); - $suggest["uid"] = $importer["importer_uid"]; - $suggest["cid"] = $importer["id"]; - $suggest["url"] = $xpath->query("dfrn:url/text()", $suggestion)->item(0)->nodeValue; - $suggest["name"] = $xpath->query("dfrn:name/text()", $suggestion)->item(0)->nodeValue; - $suggest["photo"] = $xpath->query("dfrn:photo/text()", $suggestion)->item(0)->nodeValue; - $suggest["request"] = $xpath->query("dfrn:request/text()", $suggestion)->item(0)->nodeValue; - $suggest["body"] = $xpath->query("dfrn:note/text()", $suggestion)->item(0)->nodeValue; - - // Does our member already have a friend matching this description? - - $r = q("SELECT `id` FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc($suggest["name"]), - dbesc(normalise_link($suggest["url"])), - intval($suggest["uid"]) - ); - if(count($r)) - return false; - - // Do we already have an fcontact record for this person? - - $fid = 0; - $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($suggest["url"]), - dbesc($suggest["name"]), - dbesc($suggest["request"]) - ); - if(count($r)) { - $fid = $r[0]["id"]; - - // OK, we do. Do we already have an introduction for this person ? - $r = q("SELECT `id` FROM `intro` WHERE `uid` = %d AND `fid` = %d LIMIT 1", - intval($suggest["uid"]), - intval($fid) - ); - if(count($r)) - return false; - } - if(!$fid) - $r = q("INSERT INTO `fcontact` (`name`,`url`,`photo`,`request`) VALUES ('%s', '%s', '%s', '%s')", - dbesc($suggest["name"]), - dbesc($suggest["url"]), - dbesc($suggest["photo"]), - dbesc($suggest["request"]) - ); - $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($suggest["url"]), - dbesc($suggest["name"]), - dbesc($suggest["request"]) - ); - if(count($r)) - $fid = $r[0]["id"]; - else - // database record did not get created. Quietly give up. - return false; - - - $hash = random_string(); - - $r = q("INSERT INTO `intro` (`uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked`) - VALUES(%d, %d, %d, '%s', '%s', '%s', %d)", - intval($suggest["uid"]), - intval($fid), - intval($suggest["cid"]), - dbesc($suggest["body"]), - dbesc($hash), - dbesc(datetime_convert()), - intval(0) - ); - - notification(array( - "type" => NOTIFY_SUGGEST, - "notify_flags" => $importer["notify-flags"], - "language" => $importer["language"], - "to_name" => $importer["username"], - "to_email" => $importer["email"], - "uid" => $importer["importer_uid"], - "item" => $suggest, - "link" => App::get_baseurl()."/notifications/intros", - "source_name" => $importer["name"], - "source_link" => $importer["url"], - "source_photo" => $importer["photo"], - "verb" => ACTIVITY_REQ_FRIEND, - "otype" => "intro" - )); - - return true; - - } - - private function process_relocation($xpath, $relocation, $importer) { - - logger("Processing relocations"); - - $relocate = array(); - $relocate["uid"] = $importer["importer_uid"]; - $relocate["cid"] = $importer["id"]; - $relocate["url"] = $xpath->query("dfrn:url/text()", $relocation)->item(0)->nodeValue; - $relocate["name"] = $xpath->query("dfrn:name/text()", $relocation)->item(0)->nodeValue; - $relocate["photo"] = $xpath->query("dfrn:photo/text()", $relocation)->item(0)->nodeValue; - $relocate["thumb"] = $xpath->query("dfrn:thumb/text()", $relocation)->item(0)->nodeValue; - $relocate["micro"] = $xpath->query("dfrn:micro/text()", $relocation)->item(0)->nodeValue; - $relocate["request"] = $xpath->query("dfrn:request/text()", $relocation)->item(0)->nodeValue; - $relocate["confirm"] = $xpath->query("dfrn:confirm/text()", $relocation)->item(0)->nodeValue; - $relocate["notify"] = $xpath->query("dfrn:notify/text()", $relocation)->item(0)->nodeValue; - $relocate["poll"] = $xpath->query("dfrn:poll/text()", $relocation)->item(0)->nodeValue; - $relocate["sitepubkey"] = $xpath->query("dfrn:sitepubkey/text()", $relocation)->item(0)->nodeValue; - - // update contact - $r = q("SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d;", - intval($importer["id"]), - intval($importer["importer_uid"])); - if (!$r) - return false; - - $old = $r[0]; - - $x = q("UPDATE `contact` SET - `name` = '%s', - `photo` = '%s', - `thumb` = '%s', - `micro` = '%s', - `url` = '%s', - `nurl` = '%s', - `request` = '%s', - `confirm` = '%s', - `notify` = '%s', - `poll` = '%s', - `site-pubkey` = '%s' - WHERE `id` = %d AND `uid` = %d;", - dbesc($relocate["name"]), - dbesc($relocate["photo"]), - dbesc($relocate["thumb"]), - dbesc($relocate["micro"]), - dbesc($relocate["url"]), - dbesc(normalise_link($relocate["url"])), - dbesc($relocate["request"]), - dbesc($relocate["confirm"]), - dbesc($relocate["notify"]), - dbesc($relocate["poll"]), - dbesc($relocate["sitepubkey"]), - intval($importer["id"]), - intval($importer["importer_uid"])); - - if ($x === false) - return false; - - // update items - $fields = array( - 'owner-link' => array($old["url"], $relocate["url"]), - 'author-link' => array($old["url"], $relocate["url"]), - 'owner-avatar' => array($old["photo"], $relocate["photo"]), - 'author-avatar' => array($old["photo"], $relocate["photo"]), - ); - foreach ($fields as $n=>$f){ - $x = q("UPDATE `item` SET `%s` = '%s' WHERE `%s` = '%s' AND `uid` = %d", - $n, dbesc($f[1]), - $n, dbesc($f[0]), - intval($importer["importer_uid"])); - if ($x === false) - return false; - } - - /// @TODO - /// merge with current record, current contents have priority - /// update record, set url-updated - /// update profile photos - /// schedule a scan? - return true; - } - - private function update_content($current, $item, $importer, $entrytype) { - $changed = false; - - if (edited_timestamp_is_newer($current, $item)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert("UTC","UTC",$item["edited"]) < $current["edited"]) - return(false); - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($item["title"]), - dbesc($item["body"]), - dbesc($item["tag"]), - dbesc(datetime_convert("UTC","UTC",$item["edited"])), - dbesc(datetime_convert()), - dbesc($item["uri"]), - intval($importer["importer_uid"]) - ); - create_tags_from_itemuri($item["uri"], $importer["importer_uid"]); - update_thread_uri($item["uri"], $importer["importer_uid"]); - - $changed = true; - - if ($entrytype == DFRN_REPLY_RC) - proc_run("php", "include/notifier.php","comment-import", $current["id"]); - } - - // update last-child if it changes - if($item["last-child"] AND ($item["last-child"] != $current["last-child"])) { - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($item["parent-uri"]), - intval($importer["importer_uid"]) - ); - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($item["last-child"]), - dbesc(datetime_convert()), - dbesc($item["uri"]), - intval($importer["importer_uid"]) - ); - } - return $changed; - } - - private function get_entry_type($importer, $item) { - if ($item["parent-uri"] != $item["uri"]) { - $community = false; - - if($importer["page-flags"] == PAGE_COMMUNITY || $importer["page-flags"] == PAGE_PRVGROUP) { - $sql_extra = ""; - $community = true; - logger("possible community action"); - } else - $sql_extra = " AND `contact`.`self` AND `item`.`wall` "; - - // was the top-level post for this action written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_action = false; - - $r = q("SELECT `item`.`parent-uri` FROM `item` - WHERE `item`.`uri` = '%s' - LIMIT 1", - dbesc($item["parent-uri"]) - ); - if($r && count($r)) { - $r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item` - INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' OR `item`.`thr-parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($r[0]["parent-uri"]), - dbesc($r[0]["parent-uri"]), - dbesc($r[0]["parent-uri"]), - intval($importer["importer_uid"]) - ); - if($r && count($r)) - $is_a_remote_action = true; - } - - // Does this have the characteristics of a community or private group action? - // If it's an action to a wall post on a community/prvgroup page it's a - // valid community action. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_action && $community) { - if((!$r[0]["forum_mode"]) && (!$r[0]["wall"])) { - $is_a_remote_action = false; - logger("not a community action"); - } - } - - if ($is_a_remote_action) - return DFRN_REPLY_RC; - else - return DFRN_REPLY; - - } else - return DFRN_TOP_LEVEL; - - } - - private function do_poke($item, $importer, $posted_id) { - $verb = urldecode(substr($item["verb"],strpos($item["verb"], "#")+1)); - if(!$verb) - return; - $xo = parse_xml_string($item["object"],false); - - if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { - - // somebody was poked/prodded. Was it me? - $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); - - foreach($links->link as $l) { - $atts = $l->attributes(); - switch($atts["rel"]) { - case "alternate": - $Blink = $atts["href"]; - break; - default: - break; - } - } - if($Blink && link_compare($Blink,$a->get_baseurl()."/profile/".$importer["nickname"])) { - - // send a notification - notification(array( - "type" => NOTIFY_POKE, - "notify_flags" => $importer["notify-flags"], - "language" => $importer["language"], - "to_name" => $importer["username"], - "to_email" => $importer["email"], - "uid" => $importer["importer_uid"], - "item" => $item, - "link" => $a->get_baseurl()."/display/".urlencode(get_item_guid($posted_id)), - "source_name" => stripslashes($item["author-name"]), - "source_link" => $item["author-link"], - "source_photo" => ((link_compare($item["author-link"],$importer["url"])) - ? $importer["thumb"] : $item["author-avatar"]), - "verb" => $item["verb"], - "otype" => "person", - "activity" => $verb, - "parent" => $item["parent"] - )); - } - } - } - - private function process_entry($header, $xpath, $entry, $importer) { - - logger("Processing entries"); - - $item = $header; - - // Get the uri - $item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue; - - // Fetch the owner - $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", true); - - $item["owner-name"] = $owner["name"]; - $item["owner-link"] = $owner["link"]; - $item["owner-avatar"] = $owner["avatar"]; - - // fetch the author - $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", true); - - $item["author-name"] = $author["name"]; - $item["author-link"] = $author["link"]; - $item["author-avatar"] = $author["avatar"]; - - $item["title"] = $xpath->query("atom:title/text()", $entry)->item(0)->nodeValue; - - $item["created"] = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; - $item["edited"] = $xpath->query("atom:updated/text()", $entry)->item(0)->nodeValue; - - $item["body"] = $xpath->query("dfrn:env/text()", $entry)->item(0)->nodeValue; - $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]); - // make sure nobody is trying to sneak some html tags by us - $item["body"] = notags(base64url_decode($item["body"])); - - $item["body"] = limit_body_size($item["body"]); - - /// @todo Do we need the old check for HTML elements? - - // We don't need the content element since "dfrn:env" is always present - //$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue; - - $item["last-child"] = $xpath->query("dfrn:comment-allow/text()", $entry)->item(0)->nodeValue; - $item["location"] = $xpath->query("dfrn:location/text()", $entry)->item(0)->nodeValue; - - $georsspoint = $xpath->query("georss:point", $entry); - if ($georsspoint) - $item["coord"] = $georsspoint->item(0)->nodeValue; - - $item["private"] = $xpath->query("dfrn:private/text()", $entry)->item(0)->nodeValue; - - $item["extid"] = $xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue; - - if ($xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue == "true") - $item["bookmark"] = true; - - $notice_info = $xpath->query("statusnet:notice_info", $entry); - if ($notice_info AND ($notice_info->length > 0)) { - foreach($notice_info->item(0)->attributes AS $attributes) { - if ($attributes->name == "source") - $item["app"] = strip_tags($attributes->textContent); - } - } - - $item["guid"] = $xpath->query("dfrn:diaspora_guid/text()", $entry)->item(0)->nodeValue; - - // We store the data from "dfrn:diaspora_signature" in a different table, this is done in "item_store" - $item["dsprsig"] = unxmlify($xpath->query("dfrn:diaspora_signature/text()", $entry)->item(0)->nodeValue); - - $item["verb"] = $xpath->query("activity:verb/text()", $entry)->item(0)->nodeValue; - - if ($xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue != "") - $item["object-type"] = $xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue; - - $object = $xpath->query("activity:object", $entry)->item(0); - $item["object"] = self::transform_activity($xpath, $object, "object"); - - if (trim($item["object"]) != "") { - $r = parse_xml_string($item["object"], false); - if (isset($r->type)) - $item["object-type"] = $r->type; - } - - $target = $xpath->query("activity:target", $entry)->item(0); - $item["target"] = self::transform_activity($xpath, $target, "target"); - - $categories = $xpath->query("atom:category", $entry); - if ($categories) { - foreach ($categories AS $category) { - foreach($category->attributes AS $attributes) - if ($attributes->name == "term") { - $term = $attributes->textContent; - if(strlen($item["tag"])) - $item["tag"] .= ","; - - $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; - } - } - } - - $enclosure = ""; - - $links = $xpath->query("atom:link", $entry); - if ($links) { - $rel = ""; - $href = ""; - $type = ""; - $length = "0"; - $title = ""; - foreach ($links AS $link) { - foreach($link->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "rel") - $rel = $attributes->textContent; - if ($attributes->name == "type") - $type = $attributes->textContent; - if ($attributes->name == "length") - $length = $attributes->textContent; - if ($attributes->name == "title") - $title = $attributes->textContent; - } - if (($rel != "") AND ($href != "")) - switch($rel) { - case "alternate": - $item["plink"] = $href; - break; - case "enclosure": - $enclosure = $href; - if(strlen($item["attach"])) - $item["attach"] .= ","; - - $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; - break; - } - } - } - - // Is it a reply or a top level posting? - $item["parent-uri"] = $item["uri"]; - - $inreplyto = $xpath->query("thr:in-reply-to", $entry); - if (is_object($inreplyto->item(0))) - foreach($inreplyto->item(0)->attributes AS $attributes) - if ($attributes->name == "ref") - $item["parent-uri"] = $attributes->textContent; - - // Get the type of the item (Top level post, reply or remote reply) - $entrytype = self::get_entry_type($importer, $item); - - // Now assign the rest of the values that depend on the type of the message - if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { - if (!isset($item["object-type"])) - $item["object-type"] = ACTIVITY_OBJ_COMMENT; - - if ($item["contact-id"] != $owner["contact-id"]) - $item["contact-id"] = $owner["contact-id"]; - - if (($item["network"] != $owner["network"]) AND ($owner["network"] != "")) - $item["network"] = $owner["network"]; - - if ($item["contact-id"] != $author["contact-id"]) - $item["contact-id"] = $author["contact-id"]; - - if (($item["network"] != $author["network"]) AND ($author["network"] != "")) - $item["network"] = $author["network"]; - } - - if ($entrytype == DFRN_REPLY_RC) { - $item["type"] = "remote-comment"; - $item["wall"] = 1; - } elseif ($entrytype == DFRN_TOP_LEVEL) { - // The Diaspora signature is only stored in replies - // Since this isn't a field in the item table this would create a bug when inserting this in the item table - unset($item["dsprsig"]); - - if (!isset($item["object-type"])) - $item["object-type"] = ACTIVITY_OBJ_NOTE; - - if ($item["object-type"] == ACTIVITY_OBJ_EVENT) { - logger("Item ".$item["uri"]." seems to contain an event.", LOGGER_DEBUG); - $ev = bbtoevent($item["body"]); - if((x($ev, "desc") || x($ev, "summary")) && x($ev, "start")) { - logger("Event in item ".$item["uri"]." was found.", LOGGER_DEBUG); - $ev["cid"] = $importer["id"]; - $ev["uid"] = $importer["uid"]; - $ev["uri"] = $item["uri"]; - $ev["edited"] = $item["edited"]; - $ev['private'] = $item['private']; - $ev["guid"] = $item["guid"]; - - $r = q("SELECT `id` FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item["uri"]), - intval($importer["uid"]) - ); - if(count($r)) - $ev["id"] = $r[0]["id"]; - - $event_id = event_store($ev); - logger("Event ".$event_id." was stored", LOGGER_DEBUG); - return; - } - } -/* - if(activity_match($item['verb'],ACTIVITY_FOLLOW)) { - logger('consume-feed: New follower'); - new_follower($importer,$contact,$item); - return; - } - if(activity_match($item['verb'],ACTIVITY_UNFOLLOW)) { - lose_follower($importer,$contact,$item); - return; - } - if(activity_match($item['verb'],ACTIVITY_REQ_FRIEND)) { - logger('consume-feed: New friend request'); - new_follower($importer,$contact,$item,(?),true); - return; - } - if(activity_match($item['verb'],ACTIVITY_UNFRIEND)) { - lose_sharer($importer,$contact,$item); - return; - } -*/ - } - - $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item["uri"]), - intval($importer["importer_uid"]) - ); - - // Update content if 'updated' changes - if(count($r)) { - if (self::update_content($r[0], $item, $importer, $entrytype)) - logger("Item ".$item["uri"]." was updated.", LOGGER_DEBUG); - else - logger("Item ".$item["uri"]." already existed.", LOGGER_DEBUG); - return; - } - - if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { - if($importer["rel"] == CONTACT_IS_FOLLOWER) { - logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); - return; - } - - if(($item["verb"] === ACTIVITY_LIKE) - || ($item["verb"] === ACTIVITY_DISLIKE) - || ($item["verb"] === ACTIVITY_ATTEND) - || ($item["verb"] === ACTIVITY_ATTENDNO) - || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) { - $is_like = true; - $item["type"] = "activity"; - $item["gravity"] = GRAVITY_LIKE; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($item["uid"]), - dbesc($item["author-link"]), - dbesc($item["verb"]), - dbesc($item["parent-uri"]) - ); - if($r && count($r)) - return; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($item["uid"]), - dbesc($item["author-link"]), - dbesc($item["verb"]), - dbesc($item["parent-uri"]) - ); - if($r && count($r)) - return; - - } else - $is_like = false; - - if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($item["object"],false); - $xt = parse_xml_string($item["target"],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($xt->id), - intval($importer["importer_uid"]) - ); - - if(!count($r)) - return; - - // extract tag, if not duplicate, add to parent item - if($xo->content) { - if(!(stristr($r[0]["tag"],trim($xo->content)))) { - q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", - dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), - intval($r[0]["id"]) - ); - create_tags_from_item($r[0]["id"]); - } - } - } - } - - $posted_id = item_store($item); - $parent = 0; - - if($posted_id) { - - logger("Reply from contact ".$item["contact-id"]." was stored with id ".$posted_id, LOGGER_DEBUG); - - $item["id"] = $posted_id; - - $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($posted_id), - intval($importer["importer_uid"]) - ); - if(count($r)) { - $parent = $r[0]["parent"]; - $parent_uri = $r[0]["parent-uri"]; - } - - if(!$is_like) { - $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", - dbesc(datetime_convert()), - intval($importer["importer_uid"]), - intval($r[0]["parent"]) - ); - - $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", - dbesc(datetime_convert()), - intval($importer["importer_uid"]), - intval($posted_id) - ); - } - - if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) { - logger("Notifying followers about comment ".$posted_id, LOGGER_DEBUG); - proc_run("php", "include/notifier.php", "comment-import", $posted_id); - } - - return true; - } - } else { - if(!link_compare($item["owner-link"],$importer["url"])) { - /// @todo Check if this is really used - // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - // but otherwise there's a possible data mixup on the sender's system. - // the tgroup delivery code called from item_store will correct it if it's a forum, - // but we're going to unconditionally correct it here so that the post will always be owned by our contact. - logger('Correcting item owner.', LOGGER_DEBUG); - $item["owner-name"] = $importer["senderName"]; - $item["owner-link"] = $importer["url"]; - $item["owner-avatar"] = $importer["thumb"]; - } - - if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) { - logger("Contact ".$importer["id"]." is only follower and tgroup check was negative.", LOGGER_DEBUG); - return; - } - - // This is my contact on another system, but it's really me. - // Turn this into a wall post. - $notify = item_is_remote_self($importer, $item); - - $posted_id = item_store($item, false, $notify); - - logger("Item was stored with id ".$posted_id, LOGGER_DEBUG); - - if(stristr($item["verb"],ACTIVITY_POKE)) - self::do_poke($item, $importer, $posted_id); - } - } - - private function process_deletion($header, $xpath, $deletion, $importer) { - - logger("Processing deletions"); - - foreach($deletion->attributes AS $attributes) { - if ($attributes->name == "ref") - $uri = $attributes->textContent; - if ($attributes->name == "when") - $when = $attributes->textContent; - } - if ($when) - $when = datetime_convert("UTC", "UTC", $when, "Y-m-d H:i:s"); - else - $when = datetime_convert("UTC", "UTC", "now", "Y-m-d H:i:s"); - - if (!$uri OR !$importer["id"]) - return false; - - /// @todo Only select the used fields - $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` - WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", - dbesc($uri), - intval($importer["uid"]), - intval($importer["id"]) - ); - if(!count($r)) { - logger("Item with uri ".$uri." from contact ".$importer["id"]." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG); - return; - } else { - - $item = $r[0]; - - $entrytype = self::get_entry_type($importer, $item); - - if(!$item["deleted"]) - logger('deleting item '.$item["id"].' uri='.$uri, LOGGER_DEBUG); - else - return; - - if($item["object-type"] === ACTIVITY_OBJ_EVENT) { - logger("Deleting event ".$item["event-id"], LOGGER_DEBUG); - event_delete($item["event-id"]); - } - - if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item["object"],false); - $xt = parse_xml_string($item["target"],false); - if($xt->type === ACTIVITY_OBJ_NOTE) { - $i = q("SELECT `id`, `contact-id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($xt->id), - intval($importer["importer_uid"]) - ); - if(count($i)) { - - // For tags, the owner cannot remove the tag on the author's copy of the post. - - $owner_remove = (($item["contact-id"] == $i[0]["contact-id"]) ? true: false); - $author_remove = (($item["origin"] && $item["self"]) ? true : false); - $author_copy = (($item["origin"]) ? true : false); - - if($owner_remove && $author_copy) - return; - if($author_remove || $owner_remove) { - $tags = explode(',',$i[0]["tag"]); - $newtags = array(); - if(count($tags)) { - foreach($tags as $tag) - if(trim($tag) !== trim($xo->body)) - $newtags[] = trim($tag); - } - q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", - dbesc(implode(',',$newtags)), - intval($i[0]["id"]) - ); - create_tags_from_item($i[0]["id"]); - } - } - } - } - - if($entrytype == DFRN_TOP_LEVEL) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($uri), - intval($importer["uid"]) - ); - create_tags_from_itemuri($uri, $importer["uid"]); - create_files_from_itemuri($uri, $importer["uid"]); - update_thread_uri($uri, $importer["uid"]); - } else { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($uri), - intval($importer["uid"]) - ); - create_tags_from_itemuri($uri, $importer["uid"]); - create_files_from_itemuri($uri, $importer["uid"]); - update_thread_uri($uri, $importer["importer_uid"]); - if($item["last-child"]) { - // ensure that last-child is set in case the comment that had it just got wiped. - q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", - dbesc(datetime_convert()), - dbesc($item["parent-uri"]), - intval($item["uid"]) - ); - // who is the last child now? - $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d - ORDER BY `created` DESC LIMIT 1", - dbesc($item["parent-uri"]), - intval($importer["uid"]) - ); - if(count($r)) { - q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", - intval($r[0]["id"]) - ); - } - } - // if this is a relayed delete, propagate it to other recipients - - if($entrytype == DFRN_REPLY_RC) { - logger("Notifying followers about deletion of post ".$item["id"], LOGGER_DEBUG); - proc_run("php", "include/notifier.php","drop", $item["id"]); - } - } - } - } - - /** - * @brief Imports a DFRN message - * - * @param text $xml The DFRN message - * @param array $importer Record of the importer user mixed with contact of the content - * @param bool $sort_by_date Is used when feeds are polled - */ - function import($xml,$importer, $sort_by_date = false) { - - if ($xml == "") - return; - - if($importer["readonly"]) { - // We aren't receiving stuff from this person. But we will quietly ignore them - // rather than a blatant "go away" message. - logger('ignoring contact '.$importer["id"]); - return; - } - - $doc = new DOMDocument(); - @$doc->loadXML($xml); - - $xpath = new DomXPath($doc); - $xpath->registerNamespace("atom", NAMESPACE_ATOM1); - $xpath->registerNamespace("thr", NAMESPACE_THREAD); - $xpath->registerNamespace("at", NAMESPACE_TOMB); - $xpath->registerNamespace("media", NAMESPACE_MEDIA); - $xpath->registerNamespace("dfrn", NAMESPACE_DFRN); - $xpath->registerNamespace("activity", NAMESPACE_ACTIVITY); - $xpath->registerNamespace("georss", NAMESPACE_GEORSS); - $xpath->registerNamespace("poco", NAMESPACE_POCO); - $xpath->registerNamespace("ostatus", NAMESPACE_OSTATUS); - $xpath->registerNamespace("statusnet", NAMESPACE_STATUSNET); - - $header = array(); - $header["uid"] = $importer["uid"]; - $header["network"] = NETWORK_DFRN; - $header["type"] = "remote"; - $header["wall"] = 0; - $header["origin"] = 0; - $header["contact-id"] = $importer["id"]; - - // Update the contact table if the data has changed - // Only the "dfrn:owner" in the head section contains all data - self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false); - - logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG); - - // is it a public forum? Private forums aren't supported by now with this method - $forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue); - - /// @todo Check the opposite as well (forum changed to non-forum) - if ($forum) - q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d", - intval($forum), intval($forum), - intval($importer["id"]) - ); - - $mails = $xpath->query("/atom:feed/dfrn:mail"); - foreach ($mails AS $mail) - self::process_mail($xpath, $mail, $importer); - - $suggestions = $xpath->query("/atom:feed/dfrn:suggest"); - foreach ($suggestions AS $suggestion) - self::process_suggestion($xpath, $suggestion, $importer); - - $relocations = $xpath->query("/atom:feed/dfrn:relocate"); - foreach ($relocations AS $relocation) - self::process_relocation($xpath, $relocation, $importer); - - $deletions = $xpath->query("/atom:feed/at:deleted-entry"); - foreach ($deletions AS $deletion) - self::process_deletion($header, $xpath, $deletion, $importer); - - if (!$sort_by_date) { - $entries = $xpath->query("/atom:feed/atom:entry"); - foreach ($entries AS $entry) - self::process_entry($header, $xpath, $entry, $importer); - } else { - $newentries = array(); - $entries = $xpath->query("/atom:feed/atom:entry"); - foreach ($entries AS $entry) { - $created = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; - $newentries[strtotime($created)] = $entry; - } - - // Now sort after the publishing date - ksort($newentries); - - foreach ($newentries AS $entry) - self::process_entry($header, $xpath, $entry, $importer); - } - } -} -?> diff --git a/include/ostatus.php b/include/ostatus.php index caaeec84f7..00022f8c6c 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -1312,6 +1312,10 @@ function ostatus_add_author($doc, $owner) { function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) { $a = get_app(); + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + $is_repeat = false; /* if (!$repeat) { From bcf10206359ee610c3a3f19c53d54563ae7dd8ca Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Fri, 5 Feb 2016 21:38:55 +0100 Subject: [PATCH 046/273] vier smileybutton: dark.css insert new line at the end of the file --- view/theme/vier/dark.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/theme/vier/dark.css b/view/theme/vier/dark.css index 5ddaf71a2f..d9b419e18a 100644 --- a/view/theme/vier/dark.css +++ b/view/theme/vier/dark.css @@ -66,4 +66,4 @@ li :hover { table.smiley-preview{ background-color: #252C33 !important; -} \ No newline at end of file +} From db949bb802448184bfe5164d8d3dd86ddf51b187 Mon Sep 17 00:00:00 2001 From: Andrej Stieben <e-mail@stieben.de> Date: Fri, 5 Feb 2016 21:52:39 +0100 Subject: [PATCH 047/273] Updated modules to allow for partial overrides without errors Only define functions if they have not been defined before, e.g. in themes. This makes it possible to override parts of a module and still use the other functions. --- mod/_well_known.php | 4 ++ mod/acctlink.php | 3 +- mod/acl.php | 4 +- mod/admin.php | 78 ++++++++++++++++++++++++++++----------- mod/allfriends.php | 2 + mod/amcd.php | 5 ++- mod/api.php | 11 +++--- mod/apps.php | 40 ++++++++++---------- mod/attach.php | 3 +- mod/babel.php | 46 ++++++++++++----------- mod/bookmarklet.php | 5 ++- mod/cb.php | 11 +++++- mod/common.php | 2 + mod/community.php | 13 ++++--- mod/contactgroup.php | 4 +- mod/contacts.php | 38 +++++++++++++++---- mod/content.php | 48 ++++++++++++------------ mod/credits.php | 2 + mod/crepair.php | 10 +++-- mod/delegate.php | 10 +++-- mod/dfrn_confirm.php | 3 +- mod/dfrn_notify.php | 6 ++- mod/dfrn_poll.php | 13 ++++--- mod/directory.php | 19 +++++----- mod/dirfind.php | 6 ++- mod/display.php | 9 +++-- mod/editpost.php | 5 +-- mod/events.php | 6 ++- mod/fbrowser.php | 3 +- mod/filer.php | 5 ++- mod/filerm.php | 2 + mod/follow.php | 4 ++ mod/friendica.php | 11 +++--- mod/fsuggest.php | 19 +++++----- mod/group.php | 11 ++++-- mod/hcard.php | 10 ++--- mod/help.php | 3 +- mod/hostxrd.php | 3 +- mod/ignored.php | 3 +- mod/install.php | 55 ++++++++++++++++++--------- mod/invite.php | 13 ++++--- mod/item.php | 13 +++++-- mod/like.php | 7 ++-- mod/localtime.php | 11 +++--- mod/lockview.php | 20 +++++----- mod/login.php | 4 +- mod/lostpass.php | 7 ++-- mod/maintenance.php | 3 +- mod/manage.php | 8 ++-- mod/match.php | 2 + mod/message.php | 13 +++++-- mod/modexp.php | 4 +- mod/mood.php | 10 ++--- mod/msearch.php | 9 +++-- mod/navigation.php | 3 +- mod/network.php | 17 +++++++-- mod/newmember.php | 10 +++-- mod/nodeinfo.php | 11 ++++-- mod/nogroup.php | 6 ++- mod/noscrape.php | 3 +- mod/notes.php | 20 +++++----- mod/notice.php | 9 +++-- mod/notifications.php | 6 ++- mod/notify.php | 8 ++-- mod/oembed.php | 4 +- mod/oexchange.php | 17 ++++----- mod/openid.php | 10 ++--- mod/opensearch.php | 16 ++++---- mod/ostatus_subscribe.php | 2 + mod/p.php | 4 +- mod/parse_url.php | 18 +++++++-- mod/photo.php | 2 + mod/photos.php | 15 ++++---- mod/ping.php | 4 ++ mod/poco.php | 3 +- mod/poke.php | 12 +++--- mod/post.php | 7 ++-- mod/pretheme.php | 4 +- mod/probe.php | 4 +- mod/profile.php | 7 ++-- mod/profile_photo.php | 26 ++++++------- mod/profiles.php | 14 +++++-- mod/profperm.php | 12 +++--- mod/proxy.php | 12 ++++++ mod/pubsub.php | 20 +++++----- mod/pubsubhubbub.php | 5 ++- mod/qsearch.php | 3 +- mod/randprof.php | 3 +- mod/receive.php | 4 +- mod/redir.php | 6 ++- mod/regmod.php | 9 +++-- mod/removeme.php | 6 ++- mod/repair_ostatus.php | 2 + mod/rsd_xml.php | 6 +-- mod/salmon.php | 7 +++- mod/search.php | 14 ++++--- mod/session.php | 3 +- mod/settings.php | 16 ++++---- mod/share.php | 9 ++++- mod/smilies.php | 6 ++- mod/starred.php | 3 +- mod/statistics_json.php | 2 + mod/subthread.php | 12 +++--- mod/suggest.php | 9 +++-- mod/tagger.php | 8 ++-- mod/tagrm.php | 9 +++-- mod/toggle_mobile.php | 3 +- mod/uexport.php | 30 +++++++++------ mod/uimport.php | 12 ++++-- mod/update_community.php | 5 ++- mod/update_display.php | 3 +- mod/update_network.php | 3 +- mod/update_notes.php | 9 +++-- mod/update_profile.php | 9 +++-- mod/videos.php | 12 +++--- mod/view.php | 10 +++-- mod/viewcontacts.php | 5 ++- mod/viewsrc.php | 6 +-- mod/wall_attach.php | 2 + mod/wall_upload.php | 2 + mod/wallmessage.php | 12 +++--- mod/webfinger.php | 6 +-- mod/xrd.php | 3 +- 123 files changed, 768 insertions(+), 471 deletions(-) diff --git a/mod/_well_known.php b/mod/_well_known.php index 33070a1ecd..6c33136f95 100644 --- a/mod/_well_known.php +++ b/mod/_well_known.php @@ -2,6 +2,7 @@ require_once("mod/hostxrd.php"); require_once("mod/nodeinfo.php"); +if(! function_exists('_well_known_init')) { function _well_known_init(&$a){ if ($a->argc > 1) { switch($a->argv[1]) { @@ -19,7 +20,9 @@ function _well_known_init(&$a){ http_status_exit(404); killme(); } +} +if(! function_exists('wk_social_relay')) { function wk_social_relay(&$a) { define('SR_SCOPE_ALL', 'all'); @@ -64,3 +67,4 @@ function wk_social_relay(&$a) { echo json_encode($relay, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); exit; } +} diff --git a/mod/acctlink.php b/mod/acctlink.php index a2365803ac..a551e3dbd6 100644 --- a/mod/acctlink.php +++ b/mod/acctlink.php @@ -2,8 +2,8 @@ require_once('include/Scrape.php'); +if(! function_exists('acctlink_init')) { function acctlink_init(&$a) { - if(x($_GET,'addr')) { $addr = trim($_GET['addr']); $res = probe_url($addr); @@ -14,3 +14,4 @@ function acctlink_init(&$a) { } } } +} diff --git a/mod/acl.php b/mod/acl.php index f5e04b96a7..5666ccabb8 100644 --- a/mod/acl.php +++ b/mod/acl.php @@ -3,8 +3,8 @@ require_once("include/acl_selectors.php"); +if(! function_exists('acl_init')) { function acl_init(&$a){ acl_lookup($a); } - - +} diff --git a/mod/admin.php b/mod/admin.php index 7f9000807b..ff17c0b8c4 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -2,7 +2,7 @@ /** * @file mod/admin.php - * + * * @brief Friendica admin */ @@ -23,6 +23,7 @@ require_once("include/text.php"); * @param App $a * */ +if(! function_exists('admin_post')) { function admin_post(&$a){ @@ -110,6 +111,7 @@ function admin_post(&$a){ goaway($a->get_baseurl(true) . '/admin' ); return; // NOTREACHED } +} /** * @brief Generates content of the admin panel pages @@ -128,6 +130,7 @@ function admin_post(&$a){ * @param App $a * @return string */ +if(! function_exists('admin_content')) { function admin_content(&$a) { if(!is_site_admin()) { @@ -245,6 +248,7 @@ function admin_content(&$a) { return $o; } } +} /** * @brief Subpage with some stats about "the federation" network @@ -260,6 +264,7 @@ function admin_content(&$a) { * @param App $a * @return string */ +if(! function_exists('admin_page_federation')) { function admin_page_federation(&$a) { // get counts on active friendica, diaspora, redmatrix, hubzilla, gnu // social and statusnet nodes this node is knowing @@ -284,7 +289,7 @@ function admin_page_federation(&$a) { // what versions for that platform do we know at all? // again only the active nodes $v = q('SELECT count(*) AS total, version FROM gserver - WHERE last_contact > last_failure AND platform LIKE "%s" + WHERE last_contact > last_failure AND platform LIKE "%s" GROUP BY version ORDER BY version;', $p); @@ -301,12 +306,12 @@ function admin_page_federation(&$a) { $newVC = $vv['total']; $newVV = $vv['version']; $posDash = strpos($newVV, '-'); - if($posDash) + if($posDash) $newVV = substr($newVV, 0, $posDash); if(isset($newV[$newVV])) - $newV[$newVV] += $newVC; + $newV[$newVV] += $newVC; else - $newV[$newVV] = $newVC; + $newV[$newVV] = $newVC; } foreach ($newV as $key => $value) { array_push($newVv, array('total'=>$value, 'version'=>$key)); @@ -361,6 +366,7 @@ function admin_page_federation(&$a) { '$baseurl' => $a->get_baseurl(), )); } +} /** * @brief Admin Inspect Queue Page @@ -375,6 +381,7 @@ function admin_page_federation(&$a) { * @param App $a * @return string */ +if(! function_exists('admin_page_queue')) { function admin_page_queue(&$a) { // get content from the queue table $r = q("SELECT c.name,c.nurl,q.id,q.network,q.created,q.last from queue as q, contact as c where c.id=q.cid order by q.cid, q.created;"); @@ -394,6 +401,7 @@ function admin_page_queue(&$a) { '$entries' => $r, )); } +} /** * @brief Admin Summary Page @@ -406,6 +414,7 @@ function admin_page_queue(&$a) { * @param App $a * @return string */ +if(! function_exists('admin_page_summary')) { function admin_page_summary(&$a) { $r = q("SELECT `page-flags`, COUNT(uid) as `count` FROM `user` GROUP BY `page-flags`"); $accounts = array( @@ -452,12 +461,14 @@ function admin_page_summary(&$a) { '$plugins' => array( t('Active plugins'), $a->plugins ) )); } +} /** * @brief Process send data from Admin Site Page - * + * * @param App $a */ +if(! function_exists('admin_page_site_post')) { function admin_page_site_post(&$a) { if(!x($_POST,"page_site")) { return; @@ -770,6 +781,7 @@ function admin_page_site_post(&$a) { return; // NOTREACHED } +} /** * @brief Generate Admin Site subpage @@ -779,6 +791,7 @@ function admin_page_site_post(&$a) { * @param App $a * @return string */ +if(! function_exists('admin_page_site')) { function admin_page_site(&$a) { /* Installed langs */ @@ -983,7 +996,7 @@ function admin_page_site(&$a) { '$form_security_token' => get_form_security_token("admin_site") )); - +} } /** @@ -998,6 +1011,7 @@ function admin_page_site(&$a) { * @param App $a * @return string **/ +if(! function_exists('admin_page_dbsync')) { function admin_page_dbsync(&$a) { $o = ''; @@ -1073,14 +1087,15 @@ function admin_page_dbsync(&$a) { } return $o; - +} } /** * @brief Process data send by Users admin page - * + * * @param App $a */ +if(! function_exists('admin_page_users_post')) { function admin_page_users_post(&$a){ $pending = ( x($_POST, 'pending') ? $_POST['pending'] : array() ); $users = ( x($_POST, 'user') ? $_POST['user'] : array() ); @@ -1171,6 +1186,7 @@ function admin_page_users_post(&$a){ goaway($a->get_baseurl(true) . '/admin/users' ); return; // NOTREACHED } +} /** * @brief Admin panel subpage for User management @@ -1184,6 +1200,7 @@ function admin_page_users_post(&$a){ * @param App $a * @return string */ +if(! function_exists('admin_page_users')) { function admin_page_users(&$a){ if($a->argc>2) { $uid = $a->argv[3]; @@ -1336,7 +1353,7 @@ function admin_page_users(&$a){ $o .= paginate($a); return $o; } - +} /** * @brief Plugins admin page @@ -1354,6 +1371,7 @@ function admin_page_users(&$a){ * @param App $a * @return string */ +if(! function_exists('admin_page_plugins')) { function admin_page_plugins(&$a){ /* @@ -1479,17 +1497,19 @@ function admin_page_plugins(&$a){ '$baseurl' => $a->get_baseurl(true), '$function' => 'plugins', '$plugins' => $plugins, - '$pcount' => count($plugins), + '$pcount' => count($plugins), '$noplugshint' => sprintf( t('There are currently no plugins available on your node. You can find the official plugin repository at %1$s and might find other interesting plugins in the open plugin registry at %2$s'), 'https://github.com/friendica/friendica-addons', 'http://addons.friendi.ca'), '$form_security_token' => get_form_security_token("admin_themes"), )); } +} /** * @param array $themes * @param string $th * @param int $result */ +if(! function_exists('toggle_theme')) { function toggle_theme(&$themes,$th,&$result) { for($x = 0; $x < count($themes); $x ++) { if($themes[$x]['name'] === $th) { @@ -1504,12 +1524,14 @@ function toggle_theme(&$themes,$th,&$result) { } } } +} /** * @param array $themes * @param string $th * @return int */ +if(! function_exists('theme_status')) { function theme_status($themes,$th) { for($x = 0; $x < count($themes); $x ++) { if($themes[$x]['name'] === $th) { @@ -1523,12 +1545,13 @@ function theme_status($themes,$th) { } return 0; } - +} /** * @param array $themes * @return string */ +if(! function_exists('rebuild_theme_table')) { function rebuild_theme_table($themes) { $o = ''; if(count($themes)) { @@ -1542,7 +1565,7 @@ function rebuild_theme_table($themes) { } return $o; } - +} /** * @brief Themes admin page @@ -1560,6 +1583,7 @@ function rebuild_theme_table($themes) { * @param App $a * @return string */ +if(! function_exists('admin_page_themes')) { function admin_page_themes(&$a){ $allowed_themes_str = get_config('system','allowed_themes'); @@ -1734,13 +1758,14 @@ function admin_page_themes(&$a){ '$form_security_token' => get_form_security_token("admin_themes"), )); } - +} /** * @brief Prosesses data send by Logs admin page - * + * * @param App $a */ +if(! function_exists('admin_page_logs_post')) { function admin_page_logs_post(&$a) { if(x($_POST,"page_logs")) { check_form_security_token_redirectOnErr('/admin/logs', 'admin_logs'); @@ -1758,6 +1783,7 @@ function admin_page_logs_post(&$a) { goaway($a->get_baseurl(true) . '/admin/logs' ); return; // NOTREACHED } +} /** * @brief Generates admin panel subpage for configuration of the logs @@ -1775,6 +1801,7 @@ function admin_page_logs_post(&$a) { * @param App $a * @return string */ +if(! function_exists('admin_page_logs')) { function admin_page_logs(&$a){ $log_choices = array( @@ -1806,6 +1833,7 @@ function admin_page_logs(&$a){ '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE );\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');", )); } +} /** * @brief Generates admin panel subpage to view the Friendica log @@ -1825,6 +1853,7 @@ function admin_page_logs(&$a){ * @param App $a * @return string */ +if(! function_exists('admin_page_viewlogs')) { function admin_page_viewlogs(&$a){ $t = get_markup_template("admin_viewlogs.tpl"); $f = get_config('system','logfile'); @@ -1861,12 +1890,14 @@ function admin_page_viewlogs(&$a){ '$logname' => get_config('system','logfile') )); } +} /** * @brief Prosesses data send by the features admin page - * + * * @param App $a */ +if(! function_exists('admin_page_features_post')) { function admin_page_features_post(&$a) { check_form_security_token_redirectOnErr('/admin/features', 'admin_manage_features'); @@ -1898,23 +1929,25 @@ function admin_page_features_post(&$a) { goaway($a->get_baseurl(true) . '/admin/features' ); return; // NOTREACHED } +} /** * @brief Subpage for global additional feature management - * + * * This functin generates the subpage 'Manage Additional Features' * for the admin panel. At this page the admin can set preferences - * for the user settings of the 'additional features'. If needed this + * for the user settings of the 'additional features'. If needed this * preferences can be locked through the admin. - * + * * The returned string contains the HTML code of the subpage 'Manage * Additional Features' - * + * * @param App $a * @return string */ +if(! function_exists('admin_page_features')) { function admin_page_features(&$a) { - + if((argc() > 1) && (argv(1) === 'features')) { $arr = array(); $features = get_features(false); @@ -1933,7 +1966,7 @@ function admin_page_features(&$a) { ); } } - + $tpl = get_markup_template("admin_settings_features.tpl"); $o .= replace_macros($tpl, array( '$form_security_token' => get_form_security_token("admin_manage_features"), @@ -1945,3 +1978,4 @@ function admin_page_features(&$a) { return $o; } } +} diff --git a/mod/allfriends.php b/mod/allfriends.php index 356a389b83..8843265a99 100644 --- a/mod/allfriends.php +++ b/mod/allfriends.php @@ -5,6 +5,7 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); require_once('mod/contacts.php'); +if(! function_exists('allfriends_content')) { function allfriends_content(&$a) { $o = ''; @@ -97,3 +98,4 @@ function allfriends_content(&$a) { return $o; } +} diff --git a/mod/amcd.php b/mod/amcd.php index a2a1327e6d..141a804298 100644 --- a/mod/amcd.php +++ b/mod/amcd.php @@ -1,5 +1,5 @@ <?php - +if(! function_exists('amcd_content')) { function amcd_content(&$a) { //header("Content-type: text/json"); echo <<< EOT @@ -46,4 +46,5 @@ echo <<< EOT } EOT; killme(); -} \ No newline at end of file +} +} diff --git a/mod/api.php b/mod/api.php index da2c40c305..67564836e8 100644 --- a/mod/api.php +++ b/mod/api.php @@ -1,10 +1,8 @@ <?php - require_once('include/api.php'); +if(! function_exists('oauth_get_client')) { function oauth_get_client($request){ - - $params = $request->get_parameters(); $token = $params['oauth_token']; @@ -19,9 +17,10 @@ function oauth_get_client($request){ return $r[0]; } +} +if(! function_exists('api_post')) { function api_post(&$a) { - if(! local_user()) { notice( t('Permission denied.') . EOL); return; @@ -31,9 +30,10 @@ function api_post(&$a) { notice( t('Permission denied.') . EOL); return; } - +} } +if(! function_exists('api_content')) { function api_content(&$a) { if ($a->cmd=='api/oauth/authorize'){ /* @@ -114,3 +114,4 @@ function api_content(&$a) { echo api_call($a); killme(); } +} diff --git a/mod/apps.php b/mod/apps.php index a821ef5d5b..e807feae74 100644 --- a/mod/apps.php +++ b/mod/apps.php @@ -1,25 +1,23 @@ <?php - +if(! function_exists('apps_content')) { function apps_content(&$a) { - $privateaddons = get_config('config','private_addons'); - if ($privateaddons === "1") { - if((! (local_user()))) { - info( t("You must be logged in to use addons. ")); - return;}; + $privateaddons = get_config('config','private_addons'); + if ($privateaddons === "1") { + if((! (local_user()))) { + info( t("You must be logged in to use addons. ")); + return; + } + } + + $title = t('Applications'); + + if(count($a->apps)==0) + notice( t('No installed applications.') . EOL); + + $tpl = get_markup_template("apps.tpl"); + return replace_macros($tpl, array( + '$title' => $title, + '$apps' => $a->apps, + )); } - - $title = t('Applications'); - - if(count($a->apps)==0) - notice( t('No installed applications.') . EOL); - - - $tpl = get_markup_template("apps.tpl"); - return replace_macros($tpl, array( - '$title' => $title, - '$apps' => $a->apps, - )); - - - } diff --git a/mod/attach.php b/mod/attach.php index 03f850f0d1..849faa26ec 100644 --- a/mod/attach.php +++ b/mod/attach.php @@ -1,7 +1,7 @@ <?php - require_once('include/security.php'); +if(! function_exists('attach_init')) { function attach_init(&$a) { if($a->argc != 2) { @@ -47,3 +47,4 @@ function attach_init(&$a) { killme(); // NOTREACHED } +} diff --git a/mod/babel.php b/mod/babel.php index d31e090c55..56455bdb21 100644 --- a/mod/babel.php +++ b/mod/babel.php @@ -9,55 +9,56 @@ function visible_lf($s) { return str_replace("\n",'<br />', $s); } +if(! function_exists('babel_content')) { function babel_content(&$a) { $o .= '<h1>Babel Diagnostic</h1>'; $o .= '<form action="babel" method="post">'; $o .= t('Source (bbcode) text:') . EOL . '<textarea name="text" >' . htmlspecialchars($_REQUEST['text']) .'</textarea>' . EOL; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; $o .= '<form action="babel" method="post">'; $o .= t('Source (Diaspora) text to convert to BBcode:') . EOL . '<textarea name="d2bbtext" >' . htmlspecialchars($_REQUEST['d2bbtext']) .'</textarea>' . EOL; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; if(x($_REQUEST,'text')) { $text = trim($_REQUEST['text']); - $o .= "<h2>" . t("Source input: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($text) . EOL. EOL; + $o .= "<h2>" . t("Source input: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($text) . EOL. EOL; $html = bbcode($text); - $o .= "<h2>" . t("bb2html (raw HTML): ") . "</h2>" . EOL. EOL; - $o .= htmlspecialchars($html). EOL. EOL; + $o .= "<h2>" . t("bb2html (raw HTML): ") . "</h2>" . EOL. EOL; + $o .= htmlspecialchars($html). EOL. EOL; //$html = bbcode($text); - $o .= "<h2>" . t("bb2html: ") . "</h2>" . EOL. EOL; - $o .= $html. EOL. EOL; + $o .= "<h2>" . t("bb2html: ") . "</h2>" . EOL. EOL; + $o .= $html. EOL. EOL; $bbcode = html2bbcode($html); - $o .= "<h2>" . t("bb2html2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bbcode) . EOL. EOL; + $o .= "<h2>" . t("bb2html2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bbcode) . EOL. EOL; $diaspora = bb2diaspora($text); - $o .= "<h2>" . t("bb2md: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($diaspora) . EOL. EOL; + $o .= "<h2>" . t("bb2md: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($diaspora) . EOL. EOL; $html = Markdown($diaspora); - $o .= "<h2>" . t("bb2md2html: ") . "</h2>" . EOL. EOL; - $o .= $html. EOL. EOL; + $o .= "<h2>" . t("bb2md2html: ") . "</h2>" . EOL. EOL; + $o .= $html. EOL. EOL; $bbcode = diaspora2bb($diaspora); - $o .= "<h2>" . t("bb2dia2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bbcode) . EOL. EOL; + $o .= "<h2>" . t("bb2dia2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bbcode) . EOL. EOL; $bbcode = html2bbcode($html); - $o .= "<h2>" . t("bb2md2html2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bbcode) . EOL. EOL; + $o .= "<h2>" . t("bb2md2html2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bbcode) . EOL. EOL; @@ -66,14 +67,15 @@ function babel_content(&$a) { if(x($_REQUEST,'d2bbtext')) { $d2bbtext = trim($_REQUEST['d2bbtext']); - $o .= "<h2>" . t("Source input (Diaspora format): ") . "</h2>" . EOL. EOL; - $o .= visible_lf($d2bbtext) . EOL. EOL; + $o .= "<h2>" . t("Source input (Diaspora format): ") . "</h2>" . EOL. EOL; + $o .= visible_lf($d2bbtext) . EOL. EOL; $bb = diaspora2bb($d2bbtext); - $o .= "<h2>" . t("diaspora2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bb) . EOL. EOL; + $o .= "<h2>" . t("diaspora2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bb) . EOL. EOL; } return $o; } +} diff --git a/mod/bookmarklet.php b/mod/bookmarklet.php index be8645c1fd..4db6bf401e 100644 --- a/mod/bookmarklet.php +++ b/mod/bookmarklet.php @@ -1,12 +1,14 @@ <?php - require_once('include/conversation.php'); require_once('include/items.php'); +if(! function_exists('bookmarklet_init')) { function bookmarklet_init(&$a) { $_GET["mode"] = "minimal"; } +} +if(! function_exists('bookmarklet_content')) { function bookmarklet_content(&$a) { if(!local_user()) { $o = '<h2>'.t('Login').'</h2>'; @@ -44,3 +46,4 @@ function bookmarklet_content(&$a) { return $o; } +} diff --git a/mod/cb.php b/mod/cb.php index 6375d23984..04d01302c1 100644 --- a/mod/cb.php +++ b/mod/cb.php @@ -4,21 +4,28 @@ * General purpose landing page for plugins/addons */ - +if(! function_exists('cb_init')) { function cb_init(&$a) { call_hooks('cb_init'); } +} +if(! function_exists('cb_post')) { function cb_post(&$a) { call_hooks('cb_post', $_POST); } +} +if(! function_exists('cb_afterpost')) { function cb_afterpost(&$a) { call_hooks('cb_afterpost'); } +} +if(! function_exists('cb_content')) { function cb_content(&$a) { $o = ''; call_hooks('cb_content', $o); return $o; -} \ No newline at end of file +} +} diff --git a/mod/common.php b/mod/common.php index c9409b3ef1..4cdbe9641b 100644 --- a/mod/common.php +++ b/mod/common.php @@ -5,6 +5,7 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); require_once('mod/contacts.php'); +if(! function_exists('common_content')) { function common_content(&$a) { $o = ''; @@ -144,3 +145,4 @@ function common_content(&$a) { return $o; } +} diff --git a/mod/community.php b/mod/community.php index b6d72a3555..c64c6216b1 100644 --- a/mod/community.php +++ b/mod/community.php @@ -1,15 +1,14 @@ <?php - +if(! function_exists('community_init')) { function community_init(&$a) { if(! local_user()) { unset($_SESSION['theme']); unset($_SESSION['mobile-theme']); } - - +} } - +if(! function_exists('community_content')) { function community_content(&$a, $update = 0) { $o = ''; @@ -115,7 +114,9 @@ function community_content(&$a, $update = 0) { return $o; } +} +if(! function_exists('community_getitems')) { function community_getitems($start, $itemspage) { if (get_config('system','community_page_style') == CP_GLOBAL_COMMUNITY) return(community_getpublicitems($start, $itemspage)); @@ -140,9 +141,10 @@ function community_getitems($start, $itemspage) { ); return($r); - +} } +if(! function_exists('community_getpublicitems')) { function community_getpublicitems($start, $itemspage) { $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`, `author-name` AS `name`, `owner-avatar` AS `photo`, @@ -157,3 +159,4 @@ function community_getpublicitems($start, $itemspage) { return($r); } +} diff --git a/mod/contactgroup.php b/mod/contactgroup.php index bf81afe079..0291350b21 100644 --- a/mod/contactgroup.php +++ b/mod/contactgroup.php @@ -2,6 +2,7 @@ require_once('include/group.php'); +if(! function_exists('contactgroup_content')) { function contactgroup_content(&$a) { @@ -47,4 +48,5 @@ function contactgroup_content(&$a) { } killme(); -} \ No newline at end of file +} +} diff --git a/mod/contacts.php b/mod/contacts.php index 0b421433e0..cef8bdb897 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -7,6 +7,7 @@ require_once('include/Scrape.php'); require_once('mod/proxy.php'); require_once('include/Photo.php'); +if(! function_exists('contacts_init')) { function contacts_init(&$a) { if(! local_user()) return; @@ -38,7 +39,7 @@ function contacts_init(&$a) { if (($a->data['contact']['network'] != "") AND ($a->data['contact']['network'] != NETWORK_DFRN)) { $networkname = format_network_name($a->data['contact']['network'],$a->data['contact']['url']); - } else + } else $networkname = ''; $vcard_widget = replace_macros(get_markup_template("vcard-widget.tpl"),array( @@ -88,9 +89,10 @@ function contacts_init(&$a) { '$base' => $base )); - +} } +if(! function_exists('contacts_batch_actions')) { function contacts_batch_actions(&$a){ $contacts_id = $_POST['contact_batch']; if (!is_array($contacts_id)) return; @@ -132,10 +134,10 @@ function contacts_batch_actions(&$a){ goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); else goaway($a->get_baseurl(true) . '/contacts'); - +} } - +if(! function_exists('contacts_post')) { function contacts_post(&$a) { if(! local_user()) @@ -215,10 +217,11 @@ function contacts_post(&$a) { $a->data['contact'] = $r[0]; return; - +} } /*contact actions*/ +if(! function_exists('_contact_update')) { function _contact_update($contact_id) { $r = q("SELECT `uid`, `url`, `network` FROM `contact` WHERE `id` = %d", intval($contact_id)); if (!$r) @@ -239,7 +242,9 @@ function _contact_update($contact_id) { // pull feed and consume it, which should subscribe to the hub. proc_run('php',"include/onepoll.php","$contact_id", "force"); } +} +if(! function_exists('_contact_update_profile')) { function _contact_update_profile($contact_id) { $r = q("SELECT `uid`, `url`, `network` FROM `contact` WHERE `id` = %d", intval($contact_id)); if (!$r) @@ -299,7 +304,9 @@ function _contact_update_profile($contact_id) { // Update the entry in the gcontact table update_gcontact_from_probe($data["url"]); } +} +if(! function_exists('_contact_block')) { function _contact_block($contact_id, $orig_record) { $blocked = (($orig_record['blocked']) ? 0 : 1); $r = q("UPDATE `contact` SET `blocked` = %d WHERE `id` = %d AND `uid` = %d", @@ -308,8 +315,10 @@ function _contact_block($contact_id, $orig_record) { intval(local_user()) ); return $r; - } +} + +if(! function_exists('_contact_ignore')) { function _contact_ignore($contact_id, $orig_record) { $readonly = (($orig_record['readonly']) ? 0 : 1); $r = q("UPDATE `contact` SET `readonly` = %d WHERE `id` = %d AND `uid` = %d", @@ -319,6 +328,9 @@ function _contact_ignore($contact_id, $orig_record) { ); return $r; } +} + +if(! function_exists('_contact_archive')) { function _contact_archive($contact_id, $orig_record) { $archived = (($orig_record['archive']) ? 0 : 1); $r = q("UPDATE `contact` SET `archive` = %d WHERE `id` = %d AND `uid` = %d", @@ -331,14 +343,18 @@ function _contact_archive($contact_id, $orig_record) { } return $r; } +} + +if(! function_exists('_contact_drop')) { function _contact_drop($contact_id, $orig_record) { $a = get_app(); terminate_friendship($a->user,$a->contact,$orig_record); contact_remove($orig_record['id']); } +} - +if(! function_exists('contacts_content')) { function contacts_content(&$a) { $sort_type = 0; @@ -799,7 +815,9 @@ function contacts_content(&$a) { return $o; } +} +if(! function_exists('contacts_tab')) { function contacts_tab($a, $contact_id, $active_tab) { // tabs $tabs = array( @@ -873,7 +891,9 @@ function contacts_tab($a, $contact_id, $active_tab) { return $tab_str; } +} +if(! function_exists('contact_posts')) { function contact_posts($a, $contact_id) { $r = q("SELECT `url` FROM `contact` WHERE `id` = %d", intval($contact_id)); @@ -901,7 +921,9 @@ function contact_posts($a, $contact_id) { return $o; } +} +if(! function_exists('_contact_detail_for_template')) { function _contact_detail_for_template($rr){ $community = ''; @@ -952,5 +974,5 @@ function _contact_detail_for_template($rr){ 'url' => $url, 'network' => network_to_name($rr['network'], $rr['url']), ); - +} } diff --git a/mod/content.php b/mod/content.php index c5a5556116..ab0fe7e4bf 100644 --- a/mod/content.php +++ b/mod/content.php @@ -15,7 +15,7 @@ // fast - e.g. one or two milliseconds to fetch parent items for the current content, // and 10-20 milliseconds to fetch all the child items. - +if(! function_exists('content_content')) { function content_content(&$a, $update = 0) { require_once('include/conversation.php'); @@ -61,7 +61,7 @@ function content_content(&$a, $update = 0) { $o = ''; - + $contact_id = $a->cid; @@ -100,7 +100,7 @@ function content_content(&$a, $update = 0) { $def_acl = array('allow_cid' => $str); } - + $sql_options = (($star) ? " and starred = 1 " : ''); $sql_options .= (($bmark) ? " and bookmark = 1 " : ''); @@ -137,7 +137,7 @@ function content_content(&$a, $update = 0) { } elseif($cid) { - $r = q("SELECT `id`,`name`,`network`,`writable`,`nurl` FROM `contact` WHERE `id` = %d + $r = q("SELECT `id`,`name`,`network`,`writable`,`nurl` FROM `contact` WHERE `id` = %d AND `blocked` = 0 AND `pending` = 0 LIMIT 1", intval($cid) ); @@ -304,9 +304,9 @@ function content_content(&$a, $update = 0) { echo json_encode($o); killme(); } +} - - +if(! function_exists('render_content')) { function render_content(&$a, $items, $mode, $update, $preview = false) { require_once('include/bbcode.php'); @@ -373,7 +373,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if($mode === 'network-new' || $mode === 'search' || $mode === 'community') { - // "New Item View" on network page or search page results + // "New Item View" on network page or search page results // - just loop through the items and format them minimally for display //$tpl = get_markup_template('search_item.tpl'); @@ -389,7 +389,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $sparkle = ''; if($mode === 'search' || $mode === 'community') { - if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) + if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) continue; $nickname = $item['nickname']; @@ -436,7 +436,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $drop = array( 'dropping' => $dropping, - 'select' => t('Select'), + 'select' => t('Select'), 'delete' => t('Delete'), ); @@ -526,11 +526,11 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $comments[$item['parent']] = 1; else $comments[$item['parent']] += 1; - } elseif(! x($comments,$item['parent'])) + } elseif(! x($comments,$item['parent'])) $comments[$item['parent']] = 0; // avoid notices later on } - // map all the like/dislike activities for each parent item + // map all the like/dislike activities for each parent item // Store these in the $alike and $dlike arrays foreach($items as $item) { @@ -617,14 +617,14 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $redirect_url = $a->get_baseurl($ssl_state) . '/redir/' . $item['cid'] ; - $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) + $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) ? t('Private Message') : false); // Top-level wall post not written by the wall owner (wall-to-wall) - // First figure out who owns it. + // First figure out who owns it. $osparkle = ''; @@ -651,13 +651,13 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if((! $owner_linkmatch) && (! $alias_linkmatch) && (! $owner_namematch)) { // The author url doesn't match the owner (typically the contact) - // and also doesn't match the contact alias. - // The name match is a hack to catch several weird cases where URLs are + // and also doesn't match the contact alias. + // The name match is a hack to catch several weird cases where URLs are // all over the park. It can be tricked, but this prevents you from // seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn - // well that it's the same Bob Smith. + // well that it's the same Bob Smith. - // But it could be somebody else with the same name. It just isn't highly likely. + // But it could be somebody else with the same name. It just isn't highly likely. $owner_url = $item['owner-link']; @@ -666,7 +666,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $template = $wallwall; $commentww = 'ww'; // If it is our contact, use a friendly redirect link - if((link_compare($item['owner-link'],$item['url'])) + if((link_compare($item['owner-link'],$item['url'])) && ($item['network'] === NETWORK_DFRN)) { $owner_url = $redirect_url; $osparkle = ' sparkle'; @@ -678,7 +678,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { } $likebuttons = ''; - $shareable = ((($profile_owner == local_user()) && ($item['private'] != 1)) ? true : false); + $shareable = ((($profile_owner == local_user()) && ($item['private'] != 1)) ? true : false); if($page_writeable) { /* if($toplevelpost) { */ @@ -698,7 +698,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if(($show_comment_box) || (($show_comment_box == false) && ($override_comment_box == false) && ($item['last-child']))) { $comment = replace_macros($cmnt_tpl,array( - '$return_path' => '', + '$return_path' => '', '$jsreload' => (($mode === 'display') ? $_SESSION['return_url'] : ''), '$type' => (($mode === 'profile') ? 'wall-comment' : 'net-comment'), '$id' => $item['item_id'], @@ -739,7 +739,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $drop = array( 'dropping' => $dropping, - 'select' => t('Select'), + 'select' => t('Select'), 'delete' => t('Delete'), ); @@ -805,9 +805,9 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $shiny = ""; if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0) - $shiny = 'shiny'; + $shiny = 'shiny'; - // + // localize_item($item); @@ -897,5 +897,5 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { return $threads; - +} } diff --git a/mod/credits.php b/mod/credits.php index f8cfb03f37..8e6321760b 100644 --- a/mod/credits.php +++ b/mod/credits.php @@ -5,6 +5,7 @@ * addons repository will be listed though ATM) */ +if(! function_exists('credits_content')) { function credits_content (&$a) { /* fill the page with credits */ $f = fopen('util/credits.txt','r'); @@ -18,3 +19,4 @@ function credits_content (&$a) { '$names' => $arr, )); } +} diff --git a/mod/crepair.php b/mod/crepair.php index 5b4db09dac..50502b4987 100644 --- a/mod/crepair.php +++ b/mod/crepair.php @@ -2,6 +2,7 @@ require_once("include/contact_selectors.php"); require_once("mod/contacts.php"); +if(! function_exists('crepair_init')) { function crepair_init(&$a) { if(! local_user()) return; @@ -28,8 +29,9 @@ function crepair_init(&$a) { profile_load($a, "", 0, get_contact_details_by_url($contact["url"])); } } +} - +if(! function_exists('crepair_post')) { function crepair_post(&$a) { if(! local_user()) return; @@ -91,9 +93,9 @@ function crepair_post(&$a) { return; } +} - - +if(! function_exists('crepair_content')) { function crepair_content(&$a) { if(! local_user()) { @@ -180,5 +182,5 @@ function crepair_content(&$a) { )); return $o; - +} } diff --git a/mod/delegate.php b/mod/delegate.php index 20d2e605e0..d421de3764 100644 --- a/mod/delegate.php +++ b/mod/delegate.php @@ -1,11 +1,13 @@ <?php require_once('mod/settings.php'); +if(! function_exists('delegate_init')) { function delegate_init(&$a) { return settings_init($a); } +} - +if(! function_exists('delegate_content')) { function delegate_content(&$a) { if(! local_user()) { @@ -90,12 +92,12 @@ function delegate_content(&$a) { // find every contact who might be a candidate for delegation - $r = q("select nurl from contact where substring_index(contact.nurl,'/',3) = '%s' + $r = q("select nurl from contact where substring_index(contact.nurl,'/',3) = '%s' and contact.uid = %d and contact.self = 0 and network = '%s' ", dbesc(normalise_link($a->get_baseurl())), intval(local_user()), dbesc(NETWORK_DFRN) - ); + ); if(! count($r)) { notice( t('No potential page delegates located.') . EOL); @@ -144,5 +146,5 @@ function delegate_content(&$a) { return $o; - +} } diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php index 27c04a908d..00e215e334 100644 --- a/mod/dfrn_confirm.php +++ b/mod/dfrn_confirm.php @@ -16,6 +16,7 @@ require_once('include/enotify.php'); +if(! function_exists('dfrn_confirm_post')) { function dfrn_confirm_post(&$a,$handsfree = null) { if(is_array($handsfree)) { @@ -801,5 +802,5 @@ function dfrn_confirm_post(&$a,$handsfree = null) { goaway(z_root()); // NOTREACHED - +} } diff --git a/mod/dfrn_notify.php b/mod/dfrn_notify.php index 4aa777b550..04500e89ad 100644 --- a/mod/dfrn_notify.php +++ b/mod/dfrn_notify.php @@ -5,6 +5,7 @@ require_once('include/event.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); +if(! function_exists('dfrn_notify_post')) { function dfrn_notify_post(&$a) { logger(__function__, LOGGER_TRACE); $dfrn_id = ((x($_POST,'dfrn_id')) ? notags(trim($_POST['dfrn_id'])) : ''); @@ -213,8 +214,9 @@ function dfrn_notify_post(&$a) { // NOTREACHED } +} - +if(! function_exists('dfrn_notify_content')) { function dfrn_notify_content(&$a) { if(x($_GET,'dfrn_id')) { @@ -338,5 +340,5 @@ function dfrn_notify_content(&$a) { killme(); } - +} } diff --git a/mod/dfrn_poll.php b/mod/dfrn_poll.php index ab6637607e..82c75d28cf 100644 --- a/mod/dfrn_poll.php +++ b/mod/dfrn_poll.php @@ -3,7 +3,7 @@ require_once('include/items.php'); require_once('include/auth.php'); require_once('include/dfrn.php'); - +if(! function_exists('dfrn_poll_init')) { function dfrn_poll_init(&$a) { @@ -160,7 +160,7 @@ function dfrn_poll_init(&$a) { if($final_dfrn_id != $orig_id) { logger('profile_check: ' . $final_dfrn_id . ' != ' . $orig_id, LOGGER_DEBUG); - // did not decode properly - cannot trust this site + // did not decode properly - cannot trust this site xml_status(3, 'Bad decryption'); } @@ -195,11 +195,11 @@ function dfrn_poll_init(&$a) { return; // NOTREACHED } } - +} } - +if(! function_exists('dfrn_poll_post')) { function dfrn_poll_post(&$a) { $dfrn_id = ((x($_POST,'dfrn_id')) ? $_POST['dfrn_id'] : ''); @@ -257,7 +257,7 @@ function dfrn_poll_post(&$a) { if($final_dfrn_id != $orig_id) { logger('profile_check: ' . $final_dfrn_id . ' != ' . $orig_id, LOGGER_DEBUG); - // did not decode properly - cannot trust this site + // did not decode properly - cannot trust this site xml_status(3, 'Bad decryption'); } @@ -377,7 +377,9 @@ function dfrn_poll_post(&$a) { } } +} +if(! function_exists('dfrn_poll_content')) { function dfrn_poll_content(&$a) { $dfrn_id = ((x($_GET,'dfrn_id')) ? $_GET['dfrn_id'] : ''); @@ -562,3 +564,4 @@ function dfrn_poll_content(&$a) { } } } +} diff --git a/mod/directory.php b/mod/directory.php index 294a55585d..7ce1530efc 100644 --- a/mod/directory.php +++ b/mod/directory.php @@ -1,5 +1,5 @@ <?php - +if(! function_exists('directory_init')) { function directory_init(&$a) { $a->set_pager_itemspage(60); @@ -16,23 +16,23 @@ function directory_init(&$a) { unset($_SESSION['mobile-theme']); } - +} } - +if(! function_exists('directory_post')) { function directory_post(&$a) { if(x($_POST,'search')) $a->data['search'] = $_POST['search']; } +} - - +if(! function_exists('directory_content')) { function directory_content(&$a) { global $db; require_once("mod/proxy.php"); - if((get_config('system','block_public')) && (! local_user()) && (! remote_user()) || + if((get_config('system','block_public')) && (! local_user()) && (! remote_user()) || (get_config('system','block_local_dir')) && (! local_user()) && (! remote_user())) { notice( t('Public access denied.') . EOL); return; @@ -123,14 +123,14 @@ function directory_content(&$a) { } // if(strlen($rr['dob'])) { // if(($years = age($rr['dob'],$rr['timezone'],'')) != 0) -// $details .= '<br />' . t('Age: ') . $years ; +// $details .= '<br />' . t('Age: ') . $years ; // } // if(strlen($rr['gender'])) // $details .= '<br />' . t('Gender: ') . $rr['gender']; // show if account is a community account - /// @TODO The other page types should be also respected, but first we need a good + /// @TODO The other page types should be also respected, but first we need a good /// translatiion and systemwide consistency for displaying the page type if((intval($rr['page-flags']) == PAGE_COMMUNITY) OR (intval($rr['page-flags']) == PAGE_PRVGROUP)) $community = true; @@ -158,7 +158,7 @@ function directory_content(&$a) { else { $location_e = $location; } - + $photo_menu = array(array(t("View Profile"), zrl($profile_link))); $entry = array( @@ -217,3 +217,4 @@ function directory_content(&$a) { return $o; } +} diff --git a/mod/dirfind.php b/mod/dirfind.php index 0dfe4d67a9..f5e90705b7 100644 --- a/mod/dirfind.php +++ b/mod/dirfind.php @@ -5,6 +5,7 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); require_once('mod/contacts.php'); +if(! function_exists('dirfind_init')) { function dirfind_init(&$a) { if(! local_user()) { @@ -19,9 +20,9 @@ function dirfind_init(&$a) { $a->page['aside'] .= follow_widget(); } +} - - +if(! function_exists('dirfind_content')) { function dirfind_content(&$a, $prefix = "") { $community = false; @@ -235,3 +236,4 @@ function dirfind_content(&$a, $prefix = "") { return $o; } +} diff --git a/mod/display.php b/mod/display.php index 4e33927072..9995a2b3ef 100644 --- a/mod/display.php +++ b/mod/display.php @@ -1,5 +1,5 @@ <?php - +if(! function_exists('display_init')) { function display_init(&$a) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -85,9 +85,10 @@ function display_init(&$a) { } profile_load($a, $nick, 0, $profiledata); - +} } +if(! function_exists('display_fetchauthor')) { function display_fetchauthor($a, $item) { $profiledata = array(); @@ -220,7 +221,9 @@ function display_fetchauthor($a, $item) { return($profiledata); } +} +if(! function_exists('display_content')) { function display_content(&$a, $update = 0) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -522,4 +525,4 @@ function display_content(&$a, $update = 0) { return $o; } - +} diff --git a/mod/editpost.php b/mod/editpost.php index 9a80d0b2f4..ee4d61e60a 100644 --- a/mod/editpost.php +++ b/mod/editpost.php @@ -2,6 +2,7 @@ require_once('include/acl_selectors.php'); +if(! function_exists('editpost_content')) { function editpost_content(&$a) { $o = ''; @@ -150,7 +151,5 @@ function editpost_content(&$a) { )); return $o; - } - - +} diff --git a/mod/events.php b/mod/events.php index 653ae489b8..3dc20e535a 100644 --- a/mod/events.php +++ b/mod/events.php @@ -5,6 +5,7 @@ require_once('include/datetime.php'); require_once('include/event.php'); require_once('include/items.php'); +if(! function_exists('events_post')) { function events_post(&$a) { logger('post: ' . print_r($_REQUEST,true)); @@ -156,9 +157,9 @@ function events_post(&$a) { goaway($_SESSION['return_url']); } +} - - +if(! function_exists('events_content')) { function events_content(&$a) { if(! local_user()) { @@ -578,3 +579,4 @@ function events_content(&$a) { return $o; } } +} diff --git a/mod/fbrowser.php b/mod/fbrowser.php index 0a2a7dead5..73510ef58a 100644 --- a/mod/fbrowser.php +++ b/mod/fbrowser.php @@ -10,6 +10,7 @@ require_once('include/Photo.php'); /** * @param App $a */ +if(! function_exists('fbrowser_content')) { function fbrowser_content($a){ if (!local_user()) @@ -141,5 +142,5 @@ function fbrowser_content($a){ killme(); } - +} } diff --git a/mod/filer.php b/mod/filer.php index 4e79f337dc..02b8d68978 100644 --- a/mod/filer.php +++ b/mod/filer.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); - +if(! function_exists('filer_content')) { function filer_content(&$a) { if(! local_user()) { @@ -30,8 +30,9 @@ function filer_content(&$a) { '$field' => array('term', t("Save to Folder:"), '', '', $filetags, t('- select -')), '$submit' => t('Save'), )); - + echo $o; } killme(); } +} diff --git a/mod/filerm.php b/mod/filerm.php index c266082c8f..be3456b58d 100644 --- a/mod/filerm.php +++ b/mod/filerm.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('filerm_content')) { function filerm_content(&$a) { if(! local_user()) { @@ -25,3 +26,4 @@ function filerm_content(&$a) { killme(); } +} diff --git a/mod/follow.php b/mod/follow.php index b92a0d980f..a8fdc31b5a 100644 --- a/mod/follow.php +++ b/mod/follow.php @@ -5,6 +5,7 @@ require_once('include/follow.php'); require_once('include/Contact.php'); require_once('include/contact_selectors.php'); +if(! function_exists('follow_content')) { function follow_content(&$a) { if(! local_user()) { @@ -148,7 +149,9 @@ function follow_content(&$a) { return $o; } +} +if(! function_exists('follow_post')) { function follow_post(&$a) { if(! local_user()) { @@ -185,3 +188,4 @@ function follow_post(&$a) { goaway($return_url); // NOTREACHED } +} diff --git a/mod/friendica.php b/mod/friendica.php index aad5964baf..18d045f2d5 100644 --- a/mod/friendica.php +++ b/mod/friendica.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('friendica_init')) { function friendica_init(&$a) { if ($a->argv[1]=="json"){ $register_policy = Array('REGISTER_CLOSED', 'REGISTER_APPROVE', 'REGISTER_OPEN'); @@ -56,9 +57,9 @@ function friendica_init(&$a) { killme(); } } +} - - +if(! function_exists('friendica_content')) { function friendica_content(&$a) { $o = ''; @@ -70,7 +71,7 @@ function friendica_content(&$a) { $o .= t('This is Friendica, version') . ' ' . FRIENDICA_VERSION . ' '; $o .= t('running at web location') . ' ' . z_root() . '</p><p>'; - $o .= t('Please visit <a href="http://friendica.com">Friendica.com</a> to learn more about the Friendica project.') . '</p><p>'; + $o .= t('Please visit <a href="http://friendica.com">Friendica.com</a> to learn more about the Friendica project.') . '</p><p>'; $o .= t('Bug reports and issues: please visit') . ' ' . '<a href="https://github.com/friendica/friendica/issues?state=open">'.t('the bugtracker at github').'</a></p><p>'; $o .= t('Suggestions, praise, donations, etc. - please email "Info" at Friendica - dot com') . '</p>'; @@ -102,8 +103,8 @@ function friendica_content(&$a) { else $o .= '<p>' . t('No installed plugins/addons/apps') . '</p>'; - call_hooks('about_hook', $o); + call_hooks('about_hook', $o); return $o; - +} } diff --git a/mod/fsuggest.php b/mod/fsuggest.php index 6b1cbd7533..26a5e98063 100644 --- a/mod/fsuggest.php +++ b/mod/fsuggest.php @@ -1,6 +1,6 @@ <?php - +if(! function_exists('fsuggest_post')) { function fsuggest_post(&$a) { if(! local_user()) { @@ -39,11 +39,11 @@ function fsuggest_post(&$a) { VALUES ( %d, %d, '%s','%s','%s','%s','%s','%s')", intval(local_user()), intval($contact_id), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - dbesc($r[0]['request']), - dbesc($r[0]['photo']), - dbesc($hash), + dbesc($r[0]['name']), + dbesc($r[0]['url']), + dbesc($r[0]['request']), + dbesc($r[0]['photo']), + dbesc($hash), dbesc(datetime_convert()) ); $r = q("SELECT `id` FROM `fsuggest` WHERE `note` = '%s' AND `uid` = %d LIMIT 1", @@ -65,11 +65,11 @@ function fsuggest_post(&$a) { } - +} } - +if(! function_exists('fsuggest_content')) { function fsuggest_content(&$a) { require_once('include/acl_selectors.php'); @@ -100,7 +100,7 @@ function fsuggest_content(&$a) { $o .= '<form id="fsuggest-form" action="fsuggest/' . $contact_id . '" method="post" >'; - $o .= contact_selector('suggest','suggest-select', false, + $o .= contact_selector('suggest','suggest-select', false, array('size' => 4, 'exclude' => $contact_id, 'networks' => 'DFRN_ONLY', 'single' => true)); @@ -109,3 +109,4 @@ function fsuggest_content(&$a) { return $o; } +} diff --git a/mod/group.php b/mod/group.php index 5b28784f56..2f8053eefb 100644 --- a/mod/group.php +++ b/mod/group.php @@ -1,18 +1,21 @@ <?php +if(! function_exists('validate_members')) { function validate_members(&$item) { $item = intval($item); } +} +if(! function_exists('group_init')) { function group_init(&$a) { if(local_user()) { require_once('include/group.php'); $a->page['aside'] = group_side('contacts','group','extended',(($a->argc > 1) ? intval($a->argv[1]) : 0)); } } +} - - +if(! function_exists('group_post')) { function group_post(&$a) { if(! local_user()) { @@ -64,7 +67,9 @@ function group_post(&$a) { } return; } +} +if(! function_exists('group_content')) { function group_content(&$a) { $change = false; @@ -229,5 +234,5 @@ function group_content(&$a) { } return replace_macros($tpl, $context); - +} } diff --git a/mod/hcard.php b/mod/hcard.php index 6d2d9e2ebf..af49423de3 100644 --- a/mod/hcard.php +++ b/mod/hcard.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('hcard_init')) { function hcard_init(&$a) { $blocked = (((get_config('system','block_public')) && (! local_user()) && (! remote_user())) ? true : false); @@ -15,7 +16,7 @@ function hcard_init(&$a) { $profile = 0; if((local_user()) && ($a->argc > 2) && ($a->argv[2] === 'view')) { $which = $a->user['nickname']; - $profile = $a->argv[1]; + $profile = $a->argv[1]; } profile_load($a,$which,$profile); @@ -23,7 +24,7 @@ function hcard_init(&$a) { if((x($a->profile,'page-flags')) && ($a->profile['page-flags'] == PAGE_COMMUNITY)) { $a->page['htmlhead'] .= '<meta name="friendica.community" content="true" />'; } - if(x($a->profile,'openidserver')) + if(x($a->profile,'openidserver')) $a->page['htmlhead'] .= '<link rel="openid.server" href="' . $a->profile['openidserver'] . '" />' . "\r\n"; if(x($a->profile,'openid')) { $delegate = ((strstr($a->profile['openid'],'://')) ? $a->profile['openid'] : 'http://' . $a->profile['openid']); @@ -42,10 +43,9 @@ function hcard_init(&$a) { $uri = urlencode('acct:' . $a->profile['nickname'] . '@' . $a->get_hostname() . (($a->path) ? '/' . $a->path : '')); $a->page['htmlhead'] .= '<link rel="lrdd" type="application/xrd+xml" href="' . $a->get_baseurl() . '/xrd/?uri=' . $uri . '" />' . "\r\n"; header('Link: <' . $a->get_baseurl() . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false); - + $dfrn_pages = array('request', 'confirm', 'notify', 'poll'); foreach($dfrn_pages as $dfrn) $a->page['htmlhead'] .= "<link rel=\"dfrn-{$dfrn}\" href=\"".$a->get_baseurl()."/dfrn_{$dfrn}/{$which}\" />\r\n"; - } - +} diff --git a/mod/help.php b/mod/help.php index 5465d3e900..320e622fa5 100644 --- a/mod/help.php +++ b/mod/help.php @@ -18,6 +18,7 @@ if (!function_exists('load_doc_file')) { } +if(! function_exists('help_content')) { function help_content(&$a) { nav_set_selected('help'); @@ -98,5 +99,5 @@ function help_content(&$a) { } </style>".$html; return $html; - +} } diff --git a/mod/hostxrd.php b/mod/hostxrd.php index 4121764f1a..5b178e9b8f 100644 --- a/mod/hostxrd.php +++ b/mod/hostxrd.php @@ -2,6 +2,7 @@ require_once('include/crypto.php'); +if(! function_exists('hostxrd_init')) { function hostxrd_init(&$a) { header('Access-Control-Allow-Origin: *'); header("Content-type: text/xml"); @@ -27,5 +28,5 @@ function hostxrd_init(&$a) { )); session_write_close(); exit(); - +} } diff --git a/mod/ignored.php b/mod/ignored.php index e876b4ef8b..8a681a1154 100644 --- a/mod/ignored.php +++ b/mod/ignored.php @@ -1,6 +1,6 @@ <?php - +if(! function_exists('ignored_init')) { function ignored_init(&$a) { $ignored = 0; @@ -43,3 +43,4 @@ function ignored_init(&$a) { echo json_encode($ignored); killme(); } +} diff --git a/mod/install.php b/mod/install.php index 8434b38e38..be90acba10 100755 --- a/mod/install.php +++ b/mod/install.php @@ -3,7 +3,7 @@ require_once "include/Photo.php"; $install_wizard_pass=1; - +if(! function_exists('install_init')) { function install_init(&$a){ // $baseurl/install/testrwrite to test if rewite in .htaccess is working @@ -11,20 +11,21 @@ function install_init(&$a){ echo "ok"; killme(); } - + // We overwrite current theme css, because during install we could not have a working mod_rewrite // so we could not have a css at all. Here we set a static css file for the install procedure pages $a->config['system']['theme'] = "../install"; $a->theme['stylesheet'] = $a->get_baseurl()."/view/install/style.css"; - - - + + + global $install_wizard_pass; if (x($_POST,'pass')) $install_wizard_pass = intval($_POST['pass']); - +} } +if(! function_exists('install_post')) { function install_post(&$a) { global $install_wizard_pass, $db; @@ -112,14 +113,18 @@ function install_post(&$a) { break; } } +} +if(! function_exists('get_db_errno')) { function get_db_errno() { if(class_exists('mysqli')) return mysqli_connect_errno(); else return mysql_errno(); } +} +if(! function_exists('install_content')) { function install_content(&$a) { global $install_wizard_pass, $db; @@ -304,6 +309,7 @@ function install_content(&$a) { } } +} /** * checks : array passed to template @@ -312,7 +318,8 @@ function install_content(&$a) { * required : boolean * help : string optional */ -function check_add(&$checks, $title, $status, $required, $help){ +if(! function_exists('check_add')) { +function check_add(&$checks, $title, $status, $required, $help) { $checks[] = array( 'title' => $title, 'status' => $status, @@ -320,7 +327,9 @@ function check_add(&$checks, $title, $status, $required, $help){ 'help' => $help, ); } +} +if(! function_exists('check_php')) { function check_php(&$phpath, &$checks) { $passed = $passed2 = $passed3 = false; if (strlen($phpath)){ @@ -370,9 +379,10 @@ function check_php(&$phpath, &$checks) { check_add($checks, t('PHP register_argc_argv'), $passed3, true, $help); } - +} } +if(! function_exists('check_keys')) { function check_keys(&$checks) { $help = ''; @@ -392,10 +402,10 @@ function check_keys(&$checks) { $help .= t('If running under Windows, please see "http://www.php.net/manual/en/openssl.installation.php".'); } check_add($checks, t('Generate encryption keys'), $res, true, $help); - +} } - +if(! function_exists('check_funcs')) { function check_funcs(&$checks) { $ck_funcs = array(); check_add($ck_funcs, t('libCurl PHP module'), true, true, ""); @@ -457,8 +467,9 @@ function check_funcs(&$checks) { /*if((x($_SESSION,'sysmsg')) && is_array($_SESSION['sysmsg']) && count($_SESSION['sysmsg'])) notice( t('Please see the file "INSTALL.txt".') . EOL);*/ } +} - +if(! function_exists('check_htconfig')) { function check_htconfig(&$checks) { $status = true; $help = ""; @@ -473,9 +484,10 @@ function check_htconfig(&$checks) { } check_add($checks, t('.htconfig.php is writable'), $status, false, $help); - +} } +if(! function_exists('check_smarty3')) { function check_smarty3(&$checks) { $status = true; $help = ""; @@ -489,9 +501,10 @@ function check_smarty3(&$checks) { } check_add($checks, t('view/smarty3 is writable'), $status, true, $help); - +} } +if(! function_exists('check_htaccess')) { function check_htaccess(&$checks) { $a = get_app(); $status = true; @@ -511,7 +524,9 @@ function check_htaccess(&$checks) { // cannot check modrewrite if libcurl is not installed } } +} +if(! function_exists('check_imagik')) { function check_imagik(&$checks) { $imagick = false; $gif = false; @@ -528,16 +543,18 @@ function check_imagik(&$checks) { check_add($checks, t('ImageMagick supports GIF'), $gif, false, ""); } } +} - - +if(! function_exists('manual_config')) { function manual_config(&$a) { $data = htmlentities($a->data['txt'],ENT_COMPAT,'UTF-8'); $o = t('The database configuration file ".htconfig.php" could not be written. Please use the enclosed text to create a configuration file in your web server root.'); $o .= "<textarea rows=\"24\" cols=\"80\" >$data</textarea>"; return $o; } +} +if(! function_exists('load_database_rem')) { function load_database_rem($v, $i){ $l = trim($i); if (strlen($l)>1 && ($l[0]=="-" || ($l[0]=="/" && $l[1]=="*"))){ @@ -546,8 +563,9 @@ function load_database_rem($v, $i){ return $v."\n".$i; } } +} - +if(! function_exists('load_database')) { function load_database($db) { require_once("include/dbstructure.php"); @@ -567,7 +585,9 @@ function load_database($db) { return $errors; } +} +if(! function_exists('what_next')) { function what_next() { $a = get_app(); $baseurl = $a->get_baseurl(); @@ -579,5 +599,4 @@ function what_next() { .t("Go to your new Friendica node <a href='$baseurl/register'>registration page</a> and register as new user. Remember to use the same email you have entered as administrator email. This will allow you to enter the site admin panel.") ."</p>"; } - - +} diff --git a/mod/invite.php b/mod/invite.php index ccf876c7c0..1f559dabc0 100644 --- a/mod/invite.php +++ b/mod/invite.php @@ -9,6 +9,7 @@ require_once('include/email.php'); +if(! function_exists('invite_post')) { function invite_post(&$a) { if(! local_user()) { @@ -49,7 +50,7 @@ function invite_post(&$a) { notice( sprintf( t('%s : Not a valid email address.'), $recip) . EOL); continue; } - + if($invonly && ($x || is_site_admin())) { $code = autoname(8) . srand(1000,9999); $nmessage = str_replace('$invite_code',$code,$message); @@ -70,8 +71,8 @@ function invite_post(&$a) { else $nmessage = $message; - $res = mail($recip, email_header_encode( t('Please join us on Friendica'),'UTF-8'), - $nmessage, + $res = mail($recip, email_header_encode( t('Please join us on Friendica'),'UTF-8'), + $nmessage, "From: " . $a->user['email'] . "\n" . 'Content-type: text/plain; charset=UTF-8' . "\n" . 'Content-transfer-encoding: 8bit' ); @@ -93,8 +94,9 @@ function invite_post(&$a) { notice( sprintf( tt("%d message sent.", "%d messages sent.", $total) , $total) . EOL); return; } +} - +if(! function_exists('invite_content')) { function invite_content(&$a) { if(! local_user()) { @@ -134,7 +136,7 @@ function invite_content(&$a) { '$msg_text' => t('Your message:'), '$default_message' => t('You are cordially invited to join me and other close friends on Friendica - and help us to create a better social web.') . "\r\n" . "\r\n" . $linktxt - . "\r\n" . "\r\n" . (($invonly) ? t('You will need to supply this invitation code: $invite_code') . "\r\n" . "\r\n" : '') .t('Once you have registered, please connect with me via my profile page at:') + . "\r\n" . "\r\n" . (($invonly) ? t('You will need to supply this invitation code: $invite_code') . "\r\n" . "\r\n" : '') .t('Once you have registered, please connect with me via my profile page at:') . "\r\n" . "\r\n" . $a->get_baseurl() . '/profile/' . $a->user['nickname'] . "\r\n" . "\r\n" . t('For more information about the Friendica project and why we feel it is important, please visit http://friendica.com') . "\r\n" . "\r\n" , '$submit' => t('Submit') @@ -142,3 +144,4 @@ function invite_content(&$a) { return $o; } +} diff --git a/mod/item.php b/mod/item.php index 8c5a479646..f8f2e0fafe 100644 --- a/mod/item.php +++ b/mod/item.php @@ -25,6 +25,7 @@ require_once('include/text.php'); require_once('include/items.php'); require_once('include/Scrape.php'); +if(! function_exists('item_post')) { function item_post(&$a) { if((! local_user()) && (! remote_user()) && (! x($_REQUEST,'commenter'))) @@ -1017,7 +1018,9 @@ function item_post(&$a) { item_post_return($a->get_baseurl(), $api_source, $return_path); // NOTREACHED } +} +if(! function_exists('item_post_return')) { function item_post_return($baseurl, $api_source, $return_path) { // figure out how to return, depending on from whence we came @@ -1037,9 +1040,9 @@ function item_post_return($baseurl, $api_source, $return_path) { echo json_encode($json); killme(); } +} - - +if(! function_exists('item_content')) { function item_content(&$a) { if((! local_user()) && (! remote_user())) @@ -1058,6 +1061,7 @@ function item_content(&$a) { } return $o; } +} /** * This function removes the tag $tag from the text $body and replaces it with @@ -1071,6 +1075,7 @@ function item_content(&$a) { * * @return boolean true if replaced, false if not replaced */ +if(! function_exists('handle_tag')) { function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $network = "") { require_once("include/Scrape.php"); require_once("include/socgraph.php"); @@ -1245,8 +1250,9 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo return array('replaced' => $replaced, 'contact' => $r[0]); } +} - +if(! function_exists('store_diaspora_comment_sig')) { function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item, $post_id) { // We won't be able to sign Diaspora comments for authenticated visitors - we don't have their private key @@ -1284,3 +1290,4 @@ function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item, return; } +} diff --git a/mod/like.php b/mod/like.php index 8d383b9abe..ef483a1f9e 100755 --- a/mod/like.php +++ b/mod/like.php @@ -5,6 +5,7 @@ require_once('include/bbcode.php'); require_once('include/items.php'); require_once('include/like.php'); +if(! function_exists('like_content')) { function like_content(&$a) { if(! local_user() && ! remote_user()) { return false; @@ -28,11 +29,11 @@ function like_content(&$a) { killme(); // NOTREACHED // return; // NOTREACHED } - +} // Decide how to return. If we were called with a 'return' argument, // then redirect back to the calling page. If not, just quietly end - +if(! function_exists('like_content_return')) { function like_content_return($baseurl, $return_path) { if($return_path) { @@ -45,4 +46,4 @@ function like_content_return($baseurl, $return_path) { killme(); } - +} diff --git a/mod/localtime.php b/mod/localtime.php index d1453bc527..fc500f4dd9 100644 --- a/mod/localtime.php +++ b/mod/localtime.php @@ -2,7 +2,7 @@ require_once('include/datetime.php'); - +if(! function_exists('localtime_post')) { function localtime_post(&$a) { $t = $_REQUEST['time']; @@ -13,9 +13,10 @@ function localtime_post(&$a) { if($_POST['timezone']) $a->data['mod-localtime'] = datetime_convert('UTC',$_POST['timezone'],$t,$bd_format); - +} } +if(! function_exists('localtime_content')) { function localtime_content(&$a) { $t = $_REQUEST['time']; if(! $t) @@ -38,12 +39,12 @@ function localtime_content(&$a) { $o .= '<form action ="' . $a->get_baseurl() . '/localtime?f=&time=' . $t . '" method="post" >'; - $o .= '<p>' . t('Please select your timezone:') . '</p>'; + $o .= '<p>' . t('Please select your timezone:') . '</p>'; $o .= select_timezone(($_REQUEST['timezone']) ? $_REQUEST['timezone'] : 'America/Los_Angeles'); $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form>'; return $o; - -} \ No newline at end of file +} +} diff --git a/mod/lockview.php b/mod/lockview.php index 0ae54c8c12..82f93f4985 100644 --- a/mod/lockview.php +++ b/mod/lockview.php @@ -1,8 +1,8 @@ <?php - +if(! function_exists('lockview_content')) { function lockview_content(&$a) { - + $type = (($a->argc > 1) ? $a->argv[1] : 0); if (is_numeric($type)) { $item_id = intval($type); @@ -10,13 +10,13 @@ function lockview_content(&$a) { } else { $item_id = (($a->argc > 2) ? intval($a->argv[2]) : 0); } - + if(! $item_id) killme(); if (!in_array($type, array('item','photo','event'))) killme(); - + $r = q("SELECT * FROM `%s` WHERE `id` = %d LIMIT 1", dbesc($type), intval($item_id) @@ -33,7 +33,7 @@ function lockview_content(&$a) { } - if(($item['private'] == 1) && (! strlen($item['allow_cid'])) && (! strlen($item['allow_gid'])) + if(($item['private'] == 1) && (! strlen($item['allow_cid'])) && (! strlen($item['allow_gid'])) && (! strlen($item['deny_cid'])) && (! strlen($item['deny_gid']))) { echo t('Remote privacy information not available.') . '<br />'; @@ -53,7 +53,7 @@ function lockview_content(&$a) { dbesc(implode(', ', $allowed_groups)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = '<b>' . $rr['name'] . '</b>'; } if(count($allowed_users)) { @@ -61,7 +61,7 @@ function lockview_content(&$a) { dbesc(implode(', ',$allowed_users)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = $rr['name']; } @@ -71,7 +71,7 @@ function lockview_content(&$a) { dbesc(implode(', ', $deny_groups)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = '<b><strike>' . $rr['name'] . '</strike></b>'; } if(count($deny_users)) { @@ -79,12 +79,12 @@ function lockview_content(&$a) { dbesc(implode(', ',$deny_users)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = '<strike>' . $rr['name'] . '</strike>'; } echo $o . implode(', ', $l); killme(); - +} } diff --git a/mod/login.php b/mod/login.php index d09fc1868f..47c329eb63 100644 --- a/mod/login.php +++ b/mod/login.php @@ -1,5 +1,5 @@ <?php - +if(! function_exists('login_content')) { function login_content(&$a) { if(x($_SESSION,'theme')) unset($_SESSION['theme']); @@ -9,5 +9,5 @@ function login_content(&$a) { if(local_user()) goaway(z_root()); return login(($a->config['register_policy'] == REGISTER_CLOSED) ? false : true); - +} } diff --git a/mod/lostpass.php b/mod/lostpass.php index 938d1cbb00..0c4bb1a833 100644 --- a/mod/lostpass.php +++ b/mod/lostpass.php @@ -4,6 +4,7 @@ require_once('include/email.php'); require_once('include/enotify.php'); require_once('include/text.php'); +if(! function_exists('lostpass_post')) { function lostpass_post(&$a) { $loginame = notags(trim($_POST['login-name'])); @@ -74,10 +75,10 @@ function lostpass_post(&$a) { 'body' => $body)); goaway(z_root()); - +} } - +if(! function_exists('lostpass_content')) { function lostpass_content(&$a) { @@ -164,5 +165,5 @@ function lostpass_content(&$a) { return $o; } - +} } diff --git a/mod/maintenance.php b/mod/maintenance.php index b50c94c9b9..02de29108f 100644 --- a/mod/maintenance.php +++ b/mod/maintenance.php @@ -1,7 +1,8 @@ <?php - +if(! function_exists('maintenance_content')) { function maintenance_content(&$a) { return replace_macros(get_markup_template('maintenance.tpl'), array( '$sysdown' => t('System down for maintenance') )); } +} diff --git a/mod/manage.php b/mod/manage.php index adcc3d787a..6af3db9971 100644 --- a/mod/manage.php +++ b/mod/manage.php @@ -2,7 +2,7 @@ require_once("include/text.php"); - +if(! function_exists('manage_post')) { function manage_post(&$a) { if(! local_user()) @@ -87,9 +87,9 @@ function manage_post(&$a) { goaway( $a->get_baseurl() . "/profile/" . $a->user['nickname'] ); // NOTREACHED } +} - - +if(! function_exists('manage_content')) { function manage_content(&$a) { if(! local_user()) { @@ -144,5 +144,5 @@ function manage_content(&$a) { )); return $o; - +} } diff --git a/mod/match.php b/mod/match.php index 3b0367b429..f4936b28dc 100644 --- a/mod/match.php +++ b/mod/match.php @@ -13,6 +13,7 @@ require_once('mod/proxy.php'); * @param App &$a * @return void|string */ +if(! function_exists('match_content')) { function match_content(&$a) { $o = ''; @@ -109,3 +110,4 @@ function match_content(&$a) { return $o; } +} diff --git a/mod/message.php b/mod/message.php index 1724ebc424..1f11797d8b 100644 --- a/mod/message.php +++ b/mod/message.php @@ -3,6 +3,7 @@ require_once('include/acl_selectors.php'); require_once('include/message.php'); +if(! function_exists('message_init')) { function message_init(&$a) { $tabs = ''; @@ -36,9 +37,10 @@ function message_init(&$a) { '$baseurl' => $a->get_baseurl(true), '$base' => $base )); - +} } +if(! function_exists('message_post')) { function message_post(&$a) { if(! local_user()) { @@ -91,7 +93,7 @@ function message_post(&$a) { } else goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); - +} } // Note: the code in 'item_extract_images' and 'item_redir_and_replace_images' @@ -171,7 +173,7 @@ function item_redir_and_replace_images($body, $images, $cid) { }} - +if(! function_exists('message_content')) { function message_content(&$a) { $o = ''; @@ -530,7 +532,9 @@ function message_content(&$a) { return $o; } } +} +if(! function_exists('get_messages')) { function get_messages($user, $lstart, $lend) { return q("SELECT max(`mail`.`created`) AS `mailcreated`, min(`mail`.`seen`) AS `mailseen`, @@ -541,7 +545,9 @@ function get_messages($user, $lstart, $lend) { intval($user), intval($lstart), intval($lend) ); } +} +if(! function_exists('render_messages')) { function render_messages($msg, $t) { $a = get_app(); @@ -593,3 +599,4 @@ function render_messages($msg, $t) { return $rslt; } +} diff --git a/mod/modexp.php b/mod/modexp.php index bba2c2882d..282d55a24b 100644 --- a/mod/modexp.php +++ b/mod/modexp.php @@ -2,6 +2,7 @@ require_once('library/asn1.php'); +if(! function_exists('modexp_init')) { function modexp_init(&$a) { if($a->argc != 2) @@ -29,6 +30,5 @@ function modexp_init(&$a) { echo 'RSA' . '.' . $m . '.' . $e ; killme(); - } - +} diff --git a/mod/mood.php b/mod/mood.php index eee11e20c5..2476f06562 100644 --- a/mod/mood.php +++ b/mod/mood.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); - +if(! function_exists('mood_init')) { function mood_init(&$a) { if(! local_user()) @@ -59,7 +59,7 @@ function mood_init(&$a) { $uri = item_new_uri($a->get_hostname(),$uid); - $action = sprintf( t('%1$s is currently %2$s'), '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' , $verbs[$verb]); + $action = sprintf( t('%1$s is currently %2$s'), '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' , $verbs[$verb]); $arr = array(); @@ -105,9 +105,9 @@ function mood_init(&$a) { return; } +} - - +if(! function_exists('mood_content')) { function mood_content(&$a) { if(! local_user()) { @@ -138,5 +138,5 @@ function mood_content(&$a) { )); return $o; - +} } diff --git a/mod/msearch.php b/mod/msearch.php index 89de5b7057..3b1b0b617a 100644 --- a/mod/msearch.php +++ b/mod/msearch.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('msearch_post')) { function msearch_post(&$a) { $perpage = (($_POST['n']) ? $_POST['n'] : 80); @@ -26,8 +27,8 @@ function msearch_post(&$a) { if(count($r)) { foreach($r as $rr) $results[] = array( - 'name' => $rr['name'], - 'url' => $a->get_baseurl() . '/profile/' . $rr['nickname'], + 'name' => $rr['name'], + 'url' => $a->get_baseurl() . '/profile/' . $rr['nickname'], 'photo' => $a->get_baseurl() . '/photo/avatar/' . $rr['uid'] . '.jpg', 'tags' => str_replace(array(',',' '),array(' ',' '),$rr['pub_keywords']) ); @@ -38,5 +39,5 @@ function msearch_post(&$a) { echo json_encode($output); killme(); - -} \ No newline at end of file +} +} diff --git a/mod/navigation.php b/mod/navigation.php index 5db69b171e..8fbabfda96 100644 --- a/mod/navigation.php +++ b/mod/navigation.php @@ -2,6 +2,7 @@ require_once("include/nav.php"); +if(! function_exists('navigation_content')) { function navigation_content(&$a) { $nav_info = nav_info($a); @@ -22,5 +23,5 @@ function navigation_content(&$a) { '$apps' => $a->apps, '$clear_notifs' => t('Clear notifications') )); - +} } diff --git a/mod/network.php b/mod/network.php index a07c5868ec..9b07384e1b 100644 --- a/mod/network.php +++ b/mod/network.php @@ -1,4 +1,6 @@ <?php + +if(! function_exists('network_init')) { function network_init(&$a) { if(! local_user()) { notice( t('Permission denied.') . EOL); @@ -153,9 +155,10 @@ function network_init(&$a) { $a->page['aside'] .= networks_widget($a->get_baseurl(true) . '/network',(x($_GET, 'nets') ? $_GET['nets'] : '')); $a->page['aside'] .= saved_searches($search); $a->page['aside'] .= fileas_widget($a->get_baseurl(true) . '/network',(x($_GET, 'file') ? $_GET['file'] : '')); - +} } +if(! function_exists('saved_searches')) { function saved_searches($search) { if(! feature_enabled(local_user(),'savedsearch')) @@ -204,7 +207,7 @@ function saved_searches($search) { )); return $o; - +} } /** @@ -222,6 +225,7 @@ function saved_searches($search) { * * @return Array ( $no_active, $comment_active, $postord_active, $conv_active, $new_active, $starred_active, $bookmarked_active, $spam_active ); */ +if(! function_exists('network_query_get_sel_tab')) { function network_query_get_sel_tab($a) { $no_active=''; $starred_active = ''; @@ -278,10 +282,12 @@ function network_query_get_sel_tab($a) { return array($no_active, $all_active, $postord_active, $conv_active, $new_active, $starred_active, $bookmarked_active, $spam_active); } +} /** * Return selected network from query */ +if(! function_exists('network_query_get_sel_net')) { function network_query_get_sel_net() { $network = false; @@ -291,7 +297,9 @@ function network_query_get_sel_net() { return $network; } +} +if(! function_exists('network_query_get_sel_group')) { function network_query_get_sel_group($a) { $group = false; @@ -301,8 +309,9 @@ function network_query_get_sel_group($a) { return $group; } +} - +if(! function_exists('network_content')) { function network_content(&$a, $update = 0) { require_once('include/conversation.php'); @@ -886,4 +895,4 @@ function network_content(&$a, $update = 0) { return $o; } - +} diff --git a/mod/newmember.php b/mod/newmember.php index aa55c3a098..ef25333302 100644 --- a/mod/newmember.php +++ b/mod/newmember.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('newmember_content')) { function newmember_content(&$a) { @@ -15,7 +16,7 @@ function newmember_content(&$a) { $o .= '<ul>'; - $o .= '<li> ' . '<a target="newmember" href="help/guide">' . t('Friendica Walk-Through') . '</a><br />' . t('On your <em>Quick Start</em> page - find a brief introduction to your profile and network tabs, make some new connections, and find some groups to join.') . '</li>' . EOL; + $o .= '<li> ' . '<a target="newmember" href="help/guide">' . t('Friendica Walk-Through') . '</a><br />' . t('On your <em>Quick Start</em> page - find a brief introduction to your profile and network tabs, make some new connections, and find some groups to join.') . '</li>' . EOL; $o .= '</ul>'; @@ -23,7 +24,7 @@ function newmember_content(&$a) { $o .= '<ul>'; - $o .= '<li>' . '<a target="newmember" href="settings">' . t('Go to Your Settings') . '</a><br />' . t('On your <em>Settings</em> page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web.') . '</li>' . EOL; + $o .= '<li>' . '<a target="newmember" href="settings">' . t('Go to Your Settings') . '</a><br />' . t('On your <em>Settings</em> page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web.') . '</li>' . EOL; $o .= '<li>' . t('Review the other settings, particularly the privacy settings. An unpublished directory listing is like having an unlisted phone number. In general, you should probably publish your listing - unless all of your friends and potential friends know exactly how to find you.') . '</li>' . EOL; @@ -33,7 +34,7 @@ function newmember_content(&$a) { $o .= '<ul>'; - $o .= '<li>' . '<a target="newmember" href="profile_photo">' . t('Upload Profile Photo') . '</a><br />' . t('Upload a profile photo if you have not done so already. Studies have shown that people with real photos of themselves are ten times more likely to make friends than people who do not.') . '</li>' . EOL; + $o .= '<li>' . '<a target="newmember" href="profile_photo">' . t('Upload Profile Photo') . '</a><br />' . t('Upload a profile photo if you have not done so already. Studies have shown that people with real photos of themselves are ten times more likely to make friends than people who do not.') . '</li>' . EOL; $o .= '<li>' . '<a target="newmember" href="profiles">' . t('Edit Your Profile') . '</a><br />' . t('Edit your <strong>default</strong> profile to your liking. Review the settings for hiding your list of friends and hiding the profile from unknown visitors.') . '</li>' . EOL; @@ -46,7 +47,7 @@ function newmember_content(&$a) { $o .= '<ul>'; $mail_disabled = ((function_exists('imap_open') && (! get_config('system','imap_disabled'))) ? 0 : 1); - + if(! $mail_disabled) $o .= '<li>' . '<a target="newmember" href="settings/connectors">' . t('Importing Emails') . '</a><br />' . t('Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX') . '</li>' . EOL; @@ -82,3 +83,4 @@ function newmember_content(&$a) { return $o; } +} diff --git a/mod/nodeinfo.php b/mod/nodeinfo.php index ba310a1051..7f8939182e 100644 --- a/mod/nodeinfo.php +++ b/mod/nodeinfo.php @@ -1,12 +1,13 @@ <?php /** * @file mod/nodeinfo.php - * + * * Documentation: http://nodeinfo.diaspora.software/schema.html */ require_once("include/plugin.php"); +if(! function_exists('nodeinfo_wellknown')) { function nodeinfo_wellknown(&$a) { if (!get_config("system", "nodeinfo")) { http_status_exit(404); @@ -19,7 +20,9 @@ function nodeinfo_wellknown(&$a) { echo json_encode($nodeinfo, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); exit; } +} +if(! function_exists('nodeinfo_init')) { function nodeinfo_init(&$a){ if (!get_config("system", "nodeinfo")) { http_status_exit(404); @@ -143,9 +146,9 @@ function nodeinfo_init(&$a){ echo json_encode($nodeinfo, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); exit; } +} - - +if(! function_exists('nodeinfo_cron')) { function nodeinfo_cron() { $a = get_app(); @@ -260,5 +263,5 @@ function nodeinfo_cron() { logger("cron_end"); set_config('nodeinfo','last_calucation', time()); } - +} ?> diff --git a/mod/nogroup.php b/mod/nogroup.php index 9f6e978433..818b0da77a 100644 --- a/mod/nogroup.php +++ b/mod/nogroup.php @@ -4,6 +4,7 @@ require_once('include/Contact.php'); require_once('include/socgraph.php'); require_once('include/contact_selectors.php'); +if(! function_exists('nogroup_init')) { function nogroup_init(&$a) { if(! local_user()) @@ -17,8 +18,9 @@ function nogroup_init(&$a) { $a->page['aside'] .= group_side('contacts','group','extended',0,$contact_id); } +} - +if(! function_exists('nogroup_content')) { function nogroup_content(&$a) { if(! local_user()) { @@ -66,5 +68,5 @@ function nogroup_content(&$a) { )); return $o; - +} } diff --git a/mod/noscrape.php b/mod/noscrape.php index 51bd7234cf..49fe2b9a37 100644 --- a/mod/noscrape.php +++ b/mod/noscrape.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('noscrape_init')) { function noscrape_init(&$a) { if($a->argc > 1) @@ -62,5 +63,5 @@ function noscrape_init(&$a) { header('Content-type: application/json; charset=utf-8'); echo json_encode($json_info); exit; - +} } diff --git a/mod/notes.php b/mod/notes.php index 73c1507e3e..7817e25547 100644 --- a/mod/notes.php +++ b/mod/notes.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('notes_init')) { function notes_init(&$a) { if(! local_user()) @@ -12,10 +13,10 @@ function notes_init(&$a) { nav_set_selected('home'); // profile_load($a,$which,$profile); - +} } - +if(! function_exists('notes_content')) { function notes_content(&$a,$update = false) { if(! local_user()) { @@ -69,12 +70,12 @@ function notes_content(&$a,$update = false) { // Construct permissions // default permissions - anonymous user - + $sql_extra = " AND `allow_cid` = '<" . $a->contact['id'] . ">' "; $r = q("SELECT COUNT(*) AS `total` FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 + WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 AND `item`.`type` = 'note' AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 AND `contact`.`self` = 1 AND `item`.`id` = `item`.`parent` AND `item`.`wall` = 0 @@ -90,7 +91,7 @@ function notes_content(&$a,$update = false) { $r = q("SELECT `item`.`id` AS `item_id`, `contact`.`uid` AS `contact-uid` FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 + WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 AND `item`.`type` = 'note' AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 AND `contact`.`self` = 1 AND `item`.`id` = `item`.`parent` AND `item`.`wall` = 0 @@ -109,10 +110,10 @@ function notes_content(&$a,$update = false) { foreach($r as $rr) $parents_arr[] = $rr['item_id']; $parents_str = implode(', ', $parents_arr); - - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`alias`, `contact`.`network`, `contact`.`rel`, - `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, + + $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, + `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`alias`, `contact`.`network`, `contact`.`rel`, + `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 @@ -135,3 +136,4 @@ function notes_content(&$a,$update = false) { $o .= paginate($a); return $o; } +} diff --git a/mod/notice.php b/mod/notice.php index 19cf53189a..a42d60dd40 100644 --- a/mod/notice.php +++ b/mod/notice.php @@ -1,7 +1,8 @@ <?php - /* identi.ca -> friendica items permanent-url compatibility */ - - function notice_init(&$a){ +/* identi.ca -> friendica items permanent-url compatibility */ + +if(! function_exists('notice_init')) { + function notice_init(&$a) { $id = $a->argv[1]; $r = q("SELECT user.nickname FROM user LEFT JOIN item ON item.uid=user.uid WHERE item.id=%d", intval($id) @@ -16,5 +17,5 @@ } return; - } +} diff --git a/mod/notifications.php b/mod/notifications.php index a267b7c958..c7421b2d42 100644 --- a/mod/notifications.php +++ b/mod/notifications.php @@ -3,6 +3,7 @@ include_once("include/bbcode.php"); include_once("include/contact_selectors.php"); include_once("include/Scrape.php"); +if(! function_exists('notifications_post')) { function notifications_post(&$a) { if(! local_user()) { @@ -58,11 +59,11 @@ function notifications_post(&$a) { } } } +} - - +if(! function_exists('notifications_content')) { function notifications_content(&$a) { if(! local_user()) { @@ -579,3 +580,4 @@ function notifications_content(&$a) { $o .= paginate($a); return $o; } +} diff --git a/mod/notify.php b/mod/notify.php index 02260514af..7acac1084a 100644 --- a/mod/notify.php +++ b/mod/notify.php @@ -1,6 +1,6 @@ <?php - +if(! function_exists('notify_init')) { function notify_init(&$a) { if(! local_user()) return; @@ -42,10 +42,10 @@ function notify_init(&$a) { echo $j; killme(); } - +} } - +if(! function_exists('notify_content')) { function notify_content(&$a) { if(! local_user()) return login(); @@ -80,5 +80,5 @@ function notify_content(&$a) { return $o; - +} } diff --git a/mod/oembed.php b/mod/oembed.php index cb478cb860..021cbab6fd 100644 --- a/mod/oembed.php +++ b/mod/oembed.php @@ -1,7 +1,8 @@ <?php require_once("include/oembed.php"); -function oembed_content(&$a){ +if(! function_exists('oembed_content')) { +function oembed_content(&$a) { // logger('mod_oembed ' . $a->query_string, LOGGER_ALL); if ($a->argv[1]=='b2h'){ @@ -33,3 +34,4 @@ function oembed_content(&$a){ } killme(); } +} diff --git a/mod/oexchange.php b/mod/oexchange.php index bbb436e702..1e7c9b23c9 100644 --- a/mod/oexchange.php +++ b/mod/oexchange.php @@ -1,6 +1,6 @@ <?php - +if(! function_exists('oexchange_init')) { function oexchange_init(&$a) { if(($a->argc > 1) && ($a->argv[1] === 'xrd')) { @@ -11,9 +11,10 @@ function oexchange_init(&$a) { killme(); } - +} } +if(! function_exists('oexchange_content')) { function oexchange_content(&$a) { if(! local_user()) { @@ -26,13 +27,13 @@ function oexchange_content(&$a) { return; } - $url = (((x($_REQUEST,'url')) && strlen($_REQUEST['url'])) + $url = (((x($_REQUEST,'url')) && strlen($_REQUEST['url'])) ? urlencode(notags(trim($_REQUEST['url']))) : ''); - $title = (((x($_REQUEST,'title')) && strlen($_REQUEST['title'])) + $title = (((x($_REQUEST,'title')) && strlen($_REQUEST['title'])) ? '&title=' . urlencode(notags(trim($_REQUEST['title']))) : ''); - $description = (((x($_REQUEST,'description')) && strlen($_REQUEST['description'])) + $description = (((x($_REQUEST,'description')) && strlen($_REQUEST['description'])) ? '&description=' . urlencode(notags(trim($_REQUEST['description']))) : ''); - $tags = (((x($_REQUEST,'tags')) && strlen($_REQUEST['tags'])) + $tags = (((x($_REQUEST,'tags')) && strlen($_REQUEST['tags'])) ? '&tags=' . urlencode(notags(trim($_REQUEST['tags']))) : ''); $s = fetch_url($a->get_baseurl() . '/parse_url?f=&url=' . $url . $title . $description . $tags); @@ -52,7 +53,5 @@ function oexchange_content(&$a) { $_REQUEST = $post; require_once('mod/item.php'); item_post($a); - } - - +} diff --git a/mod/openid.php b/mod/openid.php index 5d5539f00e..a92a124c0d 100644 --- a/mod/openid.php +++ b/mod/openid.php @@ -1,9 +1,8 @@ <?php - require_once('library/openid.php'); - +if(! function_exists('openid_content')) { function openid_content(&$a) { $noid = get_config('system','no_openid'); @@ -25,8 +24,8 @@ function openid_content(&$a) { goaway(z_root()); } - $r = q("SELECT `user`.*, `user`.`pubkey` as `upubkey`, `user`.`prvkey` as `uprvkey` - FROM `user` WHERE `openid` = '%s' AND `blocked` = 0 + $r = q("SELECT `user`.*, `user`.`pubkey` as `upubkey`, `user`.`prvkey` as `uprvkey` + FROM `user` WHERE `openid` = '%s' AND `blocked` = 0 AND `account_expired` = 0 AND `account_removed` = 0 AND `verified` = 1 LIMIT 1", dbesc($authid) ); @@ -40,7 +39,7 @@ function openid_content(&$a) { require_once('include/security.php'); authenticate_success($r[0],true,true); - // just in case there was no return url set + // just in case there was no return url set // and we fell through goaway(z_root()); @@ -94,3 +93,4 @@ function openid_content(&$a) { goaway(z_root()); // NOTREACHED } +} diff --git a/mod/opensearch.php b/mod/opensearch.php index ff748d1c53..f3d55a1029 100644 --- a/mod/opensearch.php +++ b/mod/opensearch.php @@ -1,18 +1,18 @@ <?php - function opensearch_content(&$a) { - +if(! function_exists('opensearch_content')) { + function opensearch_content(&$a) { $tpl = get_markup_template('opensearch.tpl'); - + header("Content-type: application/opensearchdescription+xml"); - + $o = replace_macros($tpl, array( '$baseurl' => $a->get_baseurl(), '$nodename' => $a->get_hostname(), )); - + echo $o; - + killme(); - } -?> \ No newline at end of file +} +?> diff --git a/mod/ostatus_subscribe.php b/mod/ostatus_subscribe.php index 6cca0bf679..a21436db49 100644 --- a/mod/ostatus_subscribe.php +++ b/mod/ostatus_subscribe.php @@ -3,6 +3,7 @@ require_once('include/Scrape.php'); require_once('include/follow.php'); +if(! function_exists('ostatus_subscribe_content')) { function ostatus_subscribe_content(&$a) { if(! local_user()) { @@ -76,3 +77,4 @@ function ostatus_subscribe_content(&$a) { return $o; } +} diff --git a/mod/p.php b/mod/p.php index 92b72dc1ce..225b831fea 100644 --- a/mod/p.php +++ b/mod/p.php @@ -4,7 +4,8 @@ This file is part of the Diaspora protocol. It is used for fetching single publi */ require_once("include/diaspora.php"); -function p_init($a){ +if(! function_exists('p_init')) { +function p_init($a) { if ($a->argc != 2) { header($_SERVER["SERVER_PROTOCOL"].' 510 '.t('Not Extended')); killme(); @@ -79,3 +80,4 @@ function p_init($a){ killme(); } +} diff --git a/mod/parse_url.php b/mod/parse_url.php index a1ca5a3db5..481cb89361 100644 --- a/mod/parse_url.php +++ b/mod/parse_url.php @@ -1,14 +1,14 @@ <?php -/** +/** * @file mod/parse_url.php - * + * * @todo https://developers.google.com/+/plugins/snippet/ - * + * * @verbatim * <meta itemprop="name" content="Toller Titel"> * <meta itemprop="description" content="Eine tolle Beschreibung"> * <meta itemprop="image" content="http://maple.libertreeproject.org/images/tree-icon.png"> - * + * * <body itemscope itemtype="http://schema.org/Product"> * <h1 itemprop="name">Shiny Trinket</h1> * <img itemprop="image" src="{image-url}" /> @@ -27,6 +27,7 @@ if(!function_exists('deletenode')) { } } +if(! function_exists('completeurl')) { function completeurl($url, $scheme) { $urlarr = parse_url($url); @@ -53,7 +54,9 @@ function completeurl($url, $scheme) { return($complete); } +} +if(! function_exists('parseurl_getsiteinfo_cached')) { function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = true) { if ($url == "") @@ -77,7 +80,9 @@ function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = tr return $data; } +} +if(! function_exists('parseurl_getsiteinfo')) { function parseurl_getsiteinfo($url, $no_guessing = false, $do_oembed = true, $count = 1) { require_once("include/network.php"); require_once("include/Photo.php"); @@ -400,11 +405,15 @@ function parseurl_getsiteinfo($url, $no_guessing = false, $do_oembed = true, $co return($siteinfo); } +} +if(! function_exists('arr_add_hashes')) { function arr_add_hashes(&$item,$k) { $item = '#' . $item; } +} +if(! function_exists('parse_url_content')) { function parse_url_content(&$a) { $text = null; @@ -558,4 +567,5 @@ function parse_url_content(&$a) { killme(); } +} ?> diff --git a/mod/photo.php b/mod/photo.php index 4166b4d539..3baff13db5 100644 --- a/mod/photo.php +++ b/mod/photo.php @@ -3,6 +3,7 @@ require_once('include/security.php'); require_once('include/Photo.php'); +if(! function_exists('photo_init')) { function photo_init(&$a) { global $_SERVER; @@ -209,3 +210,4 @@ function photo_init(&$a) { killme(); // NOTREACHED } +} diff --git a/mod/photos.php b/mod/photos.php index a9dade6a81..9821918e5e 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -9,6 +9,7 @@ require_once('include/redir.php'); require_once('include/tags.php'); require_once('include/threads.php'); +if(! function_exists('photos_init')) { function photos_init(&$a) { if($a->argc > 1) @@ -121,9 +122,9 @@ function photos_init(&$a) { return; } +} - - +if(! function_exists('photos_post')) { function photos_post(&$a) { logger('mod-photos: photos_post: begin' , LOGGER_DEBUG); @@ -957,9 +958,9 @@ function photos_post(&$a) { goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); // NOTREACHED } +} - - +if(! function_exists('photos_content')) { function photos_content(&$a) { // URLs: @@ -1328,7 +1329,7 @@ function photos_content(&$a) { } - /** + /** * Display one photo */ @@ -1861,7 +1862,7 @@ function photos_content(&$a) { //hide profile photos to others if((! $is_owner) && (! remote_user()) && ($rr['album'] == t('Profile Photos'))) continue; - + if($twist == 'rotright') $twist = 'rotleft'; else @@ -1906,4 +1907,4 @@ function photos_content(&$a) { $o .= paginate($a); return $o; } - +} diff --git a/mod/ping.php b/mod/ping.php index 57728d3294..dbc000a8a5 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -5,6 +5,7 @@ require_once('include/forums.php'); require_once('include/group.php'); require_once("mod/proxy.php"); +if(! function_exists('ping_init')) { function ping_init(&$a) { header("Content-type: text/xml"); @@ -338,7 +339,9 @@ function ping_init(&$a) { killme(); } +} +if(! function_exists('ping_get_notifications')) { function ping_get_notifications($uid) { $result = array(); @@ -406,3 +409,4 @@ function ping_get_notifications($uid) { return($result); } +} diff --git a/mod/poco.php b/mod/poco.php index 0a1b392169..4b04d70138 100644 --- a/mod/poco.php +++ b/mod/poco.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('poco_init')) { function poco_init(&$a) { require_once("include/bbcode.php"); @@ -324,5 +325,5 @@ function poco_init(&$a) { else http_status_exit(500); - +} } diff --git a/mod/poke.php b/mod/poke.php index 45a577cda6..1af78b68ed 100644 --- a/mod/poke.php +++ b/mod/poke.php @@ -4,11 +4,11 @@ * * Poke, prod, finger, or otherwise do unspeakable things to somebody - who must be a connection in your address book * This function can be invoked with the required arguments (verb and cid and private and possibly parent) silently via ajax or - * other web request. You must be logged in and connected to a profile. + * other web request. You must be logged in and connected to a profile. * If the required arguments aren't present, we'll display a simple form to choose a recipient and a verb. * parent is a special argument which let's you attach this activity as a comment to an existing conversation, which * may have started with somebody else poking (etc.) somebody, but this isn't necessary. This can be used in the more pokes - * plugin version to have entire conversations where Alice poked Bob, Bob fingered Alice, Alice hugged Bob, etc. + * plugin version to have entire conversations where Alice poked Bob, Bob fingered Alice, Alice hugged Bob, etc. * * private creates a private conversation with the recipient. Otherwise your profile's default post privacy is used. * @@ -18,7 +18,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); - +if(! function_exists('poke_init')) { function poke_init(&$a) { if(! local_user()) @@ -140,9 +140,9 @@ function poke_init(&$a) { return; } +} - - +if(! function_exists('poke_content')) { function poke_content(&$a) { if(! local_user()) { @@ -201,5 +201,5 @@ function poke_content(&$a) { )); return $o; - +} } diff --git a/mod/post.php b/mod/post.php index c0e783a6aa..631bf0eba6 100644 --- a/mod/post.php +++ b/mod/post.php @@ -9,7 +9,8 @@ require_once('include/salmon.php'); require_once('include/crypto.php'); // not yet ready for prime time //require_once('include/zot.php'); - + +if(! function_exists('post_post')) { function post_post(&$a) { $bulk_delivery = false; @@ -19,7 +20,7 @@ function post_post(&$a) { } else { $nickname = $a->argv[2]; - $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' + $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nickname) ); @@ -48,4 +49,4 @@ function post_post(&$a) { http_status_exit(($ret) ? $ret : 200); // NOTREACHED } - +} diff --git a/mod/pretheme.php b/mod/pretheme.php index 4584cb29e2..5d1c261fcb 100644 --- a/mod/pretheme.php +++ b/mod/pretheme.php @@ -1,7 +1,8 @@ <?php +if(! function_exists('pretheme_init')) { function pretheme_init(&$a) { - + if($_REQUEST['theme']) { $theme = $_REQUEST['theme']; $info = get_theme_info($theme); @@ -20,3 +21,4 @@ function pretheme_init(&$a) { } killme(); } +} diff --git a/mod/probe.php b/mod/probe.php index c95db291b3..fcf83e7603 100644 --- a/mod/probe.php +++ b/mod/probe.php @@ -2,13 +2,14 @@ require_once('include/Scrape.php'); +if(! function_exists('probe_content')) { function probe_content(&$a) { $o .= '<h3>Probe Diagnostic</h3>'; $o .= '<form action="probe" method="get">'; $o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] .'" />'; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; @@ -22,3 +23,4 @@ function probe_content(&$a) { } return $o; } +} diff --git a/mod/profile.php b/mod/profile.php index 26bd395230..b02570d5af 100644 --- a/mod/profile.php +++ b/mod/profile.php @@ -3,7 +3,7 @@ require_once('include/contact_widgets.php'); require_once('include/redir.php'); - +if(! function_exists('profile_init')) { function profile_init(&$a) { if(! x($a->page,'aside')) @@ -65,10 +65,10 @@ function profile_init(&$a) { foreach($dfrn_pages as $dfrn) $a->page['htmlhead'] .= "<link rel=\"dfrn-{$dfrn}\" href=\"".$a->get_baseurl()."/dfrn_{$dfrn}/{$which}\" />\r\n"; $a->page['htmlhead'] .= "<link rel=\"dfrn-poco\" href=\"".$a->get_baseurl()."/poco/{$which}\" />\r\n"; - +} } - +if(! function_exists('profile_content')) { function profile_content(&$a, $update = 0) { $category = $datequery = $datequery2 = ''; @@ -350,3 +350,4 @@ function profile_content(&$a, $update = 0) { return $o; } +} diff --git a/mod/profile_photo.php b/mod/profile_photo.php index 4e8d279a97..e3d6adb491 100644 --- a/mod/profile_photo.php +++ b/mod/profile_photo.php @@ -2,6 +2,7 @@ require_once("include/Photo.php"); +if(! function_exists('profile_photo_init')) { function profile_photo_init(&$a) { if(! local_user()) { @@ -9,10 +10,10 @@ function profile_photo_init(&$a) { } profile_load($a,$a->user['nickname']); - +} } - +if(! function_exists('profile_photo_post')) { function profile_photo_post(&$a) { if(! local_user()) { @@ -143,7 +144,7 @@ function profile_photo_post(&$a) { $filesize = intval($_FILES['userfile']['size']); $filetype = $_FILES['userfile']['type']; if ($filetype=="") $filetype=guess_image_type($filename); - + $maximagesize = get_config('system','maximagesize'); if(($maximagesize) && ($filesize > $maximagesize)) { @@ -164,7 +165,7 @@ function profile_photo_post(&$a) { $ph->orient($src); @unlink($src); return profile_photo_crop_ui_head($a, $ph); - +} } @@ -175,7 +176,7 @@ function profile_photo_content(&$a) { notice( t('Permission denied.') . EOL ); return; } - + $newuser = false; if($a->argc == 2 && $a->argv[1] === 'new') @@ -186,9 +187,9 @@ function profile_photo_content(&$a) { notice( t('Permission denied.') . EOL ); return; }; - + // check_form_security_token_redirectOnErr('/profile_photo', 'profile_photo'); - + $resource_id = $a->argv[2]; //die(":".local_user()); $r=q("SELECT * FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' ORDER BY `scale` ASC", @@ -240,7 +241,7 @@ function profile_photo_content(&$a) { if(! x($a->config,'imagecrop')) { - + $tpl = get_markup_template('profile_photo.tpl'); $o .= replace_macros($tpl,array( @@ -295,11 +296,11 @@ function profile_photo_crop_ui_head(&$a, $ph){ } $hash = photo_new_resource(); - + $smallest = 0; - $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 0 ); + $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 0 ); if($r) info( t('Image uploaded successfully.') . EOL ); @@ -308,8 +309,8 @@ function profile_photo_crop_ui_head(&$a, $ph){ if($width > 640 || $height > 640) { $ph->scaleImage(640); - $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 1 ); - + $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 1 ); + if($r === false) notice( sprintf(t('Image size reduction [%s] failed.'),"640") . EOL ); else @@ -323,4 +324,3 @@ function profile_photo_crop_ui_head(&$a, $ph){ $a->page['end'] .= replace_macros(get_markup_template("cropend.tpl"), array()); return; }} - diff --git a/mod/profiles.php b/mod/profiles.php index 5c372de8ee..9ce478ba19 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -1,6 +1,7 @@ <?php require_once("include/Contact.php"); +if(! function_exists('profiles_init')) { function profiles_init(&$a) { nav_set_selected('profiles'); @@ -139,9 +140,10 @@ function profiles_init(&$a) { } - +} } +if(! function_exists('profile_clean_keywords')) { function profile_clean_keywords($keywords) { $keywords = str_replace(","," ",$keywords); $keywords = explode(" ", $keywords); @@ -158,7 +160,9 @@ function profile_clean_keywords($keywords) { return $keywords; } +} +if(! function_exists('profiles_post')) { function profiles_post(&$a) { if(! local_user()) { @@ -502,8 +506,9 @@ function profiles_post(&$a) { } } } +} - +if(! function_exists('profile_activity')) { function profile_activity($changed, $value) { $a = get_app(); @@ -593,8 +598,9 @@ function profile_activity($changed, $value) { } } +} - +if(! function_exists('profiles_content')) { function profiles_content(&$a) { if(! local_user()) { @@ -818,5 +824,5 @@ function profiles_content(&$a) { } return $o; } - +} } diff --git a/mod/profperm.php b/mod/profperm.php index 077f695bea..6fb7172949 100644 --- a/mod/profperm.php +++ b/mod/profperm.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('profperm_init')) { function profperm_init(&$a) { if(! local_user()) @@ -9,10 +10,10 @@ function profperm_init(&$a) { $profile = $a->argv[1]; profile_load($a,$which,$profile); - +} } - +if(! function_exists('profperm_content')) { function profperm_content(&$a) { if(! local_user()) { @@ -108,9 +109,9 @@ function profperm_content(&$a) { } $o .= '<div id="prof-update-wrapper">'; - if($change) + if($change) $o = ''; - + $o .= '<div id="prof-members-title">'; $o .= '<h3>' . t('Visible To') . '</h3>'; $o .= '</div>'; @@ -156,6 +157,5 @@ function profperm_content(&$a) { } $o .= '</div>'; return $o; - } - +} diff --git a/mod/proxy.php b/mod/proxy.php index abcaf49127..8e2a389254 100644 --- a/mod/proxy.php +++ b/mod/proxy.php @@ -12,6 +12,7 @@ define("PROXY_SIZE_LARGE", "large"); require_once('include/security.php'); require_once("include/Photo.php"); +if(! function_exists('proxy_init')) { function proxy_init() { global $a, $_SERVER; @@ -232,7 +233,9 @@ function proxy_init() { killme(); } +} +if(! function_exists('proxy_url')) { function proxy_url($url, $writemode = false, $size = "") { global $_SERVER; @@ -294,11 +297,13 @@ function proxy_url($url, $writemode = false, $size = "") { else return ($proxypath.$size); } +} /** * @param $url string * @return boolean */ +if(! function_exists('proxy_is_local_image')) { function proxy_is_local_image($url) { if ($url[0] == '/') return true; @@ -309,7 +314,9 @@ function proxy_is_local_image($url) { $url = normalise_link($url); return (substr($url, 0, strlen($baseurl)) == $baseurl); } +} +if(! function_exists('proxy_parse_query')) { function proxy_parse_query($var) { /** * Use this function to parse out the query array element from @@ -328,7 +335,9 @@ function proxy_parse_query($var) { unset($val, $x, $var); return $arr; } +} +if(! function_exists('proxy_img_cb')) { function proxy_img_cb($matches) { // if the picture seems to be from another picture cache then take the original source @@ -342,10 +351,13 @@ function proxy_img_cb($matches) { return $matches[1].proxy_url(htmlspecialchars_decode($matches[2])).$matches[3]; } +} +if(! function_exists('proxy_parse_html')) { function proxy_parse_html($html) { $a = get_app(); $html = str_replace(normalise_link($a->get_baseurl())."/", $a->get_baseurl()."/", $html); return preg_replace_callback("/(<img [^>]*src *= *[\"'])([^\"']+)([\"'][^>]*>)/siU", "proxy_img_cb", $html); } +} diff --git a/mod/pubsub.php b/mod/pubsub.php index beb73b4e2c..15523e637a 100644 --- a/mod/pubsub.php +++ b/mod/pubsub.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('hub_return')) { function hub_return($valid,$body) { if($valid) { @@ -14,18 +15,18 @@ function hub_return($valid,$body) { // NOTREACHED } +} // when receiving an XML feed, always return OK - +if(! function_exists('hub_post_return')) { function hub_post_return() { - header($_SERVER["SERVER_PROTOCOL"] . ' 200 ' . 'OK'); killme(); - +} } - +if(! function_exists('pubsub_init')) { function pubsub_init(&$a) { $nick = (($a->argc > 1) ? notags(trim($a->argv[1])) : ''); @@ -57,7 +58,7 @@ function pubsub_init(&$a) { $sql_extra = ((strlen($hub_verify)) ? sprintf(" AND `hub-verify` = '%s' ", dbesc($hub_verify)) : ''); - $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d + $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d AND `blocked` = 0 AND `pending` = 0 $sql_extra LIMIT 1", intval($contact_id), intval($owner['uid']) @@ -75,7 +76,7 @@ function pubsub_init(&$a) { $contact = $r[0]; - // We must initiate an unsubscribe request with a verify_token. + // We must initiate an unsubscribe request with a verify_token. // Don't allow outsiders to unsubscribe us. if($hub_mode === 'unsubscribe') { @@ -95,9 +96,11 @@ function pubsub_init(&$a) { hub_return(true, $hub_challenge); } } +} require_once('include/security.php'); +if(! function_exists('pubsub_post')) { function pubsub_post(&$a) { $xml = file_get_contents('php://input'); @@ -155,8 +158,5 @@ function pubsub_post(&$a) { consume_feed($xml,$importer,$contact,$feedhub,1,2); hub_post_return(); - } - - - +} diff --git a/mod/pubsubhubbub.php b/mod/pubsubhubbub.php index 5d7621cc74..b0e3ef3099 100644 --- a/mod/pubsubhubbub.php +++ b/mod/pubsubhubbub.php @@ -1,9 +1,12 @@ <?php +if(! function_exists('post_var')) { function post_var($name) { return (x($_POST, $name)) ? notags(trim($_POST[$name])) : ''; } +} +if(! function_exists('pubsubhubbub_init')) { function pubsubhubbub_init(&$a) { // PuSH subscription must be considered "public" so just block it // if public access isn't enabled. @@ -158,5 +161,5 @@ function pubsubhubbub_init(&$a) { killme(); } - +} ?> diff --git a/mod/qsearch.php b/mod/qsearch.php index c35e253b67..cffc3e50ba 100644 --- a/mod/qsearch.php +++ b/mod/qsearch.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('qsearch_init')) { function qsearch_init(&$a) { if(! local_user()) @@ -47,4 +48,4 @@ function qsearch_init(&$a) { echo json_encode((object) $results); killme(); } - +} diff --git a/mod/randprof.php b/mod/randprof.php index 6713a81d9e..e9e0a8e7bb 100644 --- a/mod/randprof.php +++ b/mod/randprof.php @@ -1,6 +1,6 @@ <?php - +if(! function_exists('randprof_init')) { function randprof_init(&$a) { require_once('include/Contact.php'); $x = random_profile(); @@ -8,3 +8,4 @@ function randprof_init(&$a) { goaway(zrl($x)); goaway($a->get_baseurl() . '/profile'); } +} diff --git a/mod/receive.php b/mod/receive.php index 95a5101675..3a30058cdc 100644 --- a/mod/receive.php +++ b/mod/receive.php @@ -9,7 +9,7 @@ require_once('include/salmon.php'); require_once('include/crypto.php'); require_once('include/diaspora.php'); - +if(! function_exists('receive_post')) { function receive_post(&$a) { @@ -73,4 +73,4 @@ function receive_post(&$a) { http_status_exit(($ret) ? $ret : 200); // NOTREACHED } - +} diff --git a/mod/redir.php b/mod/redir.php index 632c395786..2dda0571b2 100644 --- a/mod/redir.php +++ b/mod/redir.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('redir_init')) { function redir_init(&$a) { $url = ((x($_GET,'url')) ? $_GET['url'] : ''); @@ -57,9 +58,9 @@ function redir_init(&$a) { intval(time() + 45) ); - logger('mod_redir: ' . $r[0]['name'] . ' ' . $sec, LOGGER_DEBUG); + logger('mod_redir: ' . $r[0]['name'] . ' ' . $sec, LOGGER_DEBUG); $dest = (($url) ? '&destination_url=' . $url : ''); - goaway ($r[0]['poll'] . '?dfrn_id=' . $dfrn_id + goaway ($r[0]['poll'] . '?dfrn_id=' . $dfrn_id . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&type=profile&sec=' . $sec . $dest . $quiet ); } @@ -75,3 +76,4 @@ function redir_init(&$a) { goaway(z_root()); } +} diff --git a/mod/regmod.php b/mod/regmod.php index 5a90db1f90..2e3ca414e3 100644 --- a/mod/regmod.php +++ b/mod/regmod.php @@ -3,6 +3,7 @@ require_once('include/enotify.php'); require_once('include/user.php'); +if(! function_exists('user_allow')) { function user_allow($hash) { $a = get_app(); @@ -55,14 +56,14 @@ function user_allow($hash) { info( t('Account approved.') . EOL ); return true; } - +} } // This does not have to go through user_remove() and save the nickname // permanently against re-registration, as the person was not yet // allowed to have friends on this system - +if(! function_exists('user_deny')) { function user_deny($hash) { $register = q("SELECT * FROM `register` WHERE `hash` = '%s' LIMIT 1", @@ -91,9 +92,10 @@ function user_deny($hash) { ); notice( sprintf(t('Registration revoked for %s'), $user[0]['username']) . EOL); return true; - +} } +if(! function_exists('regmod_content')) { function regmod_content(&$a) { global $lang; @@ -131,3 +133,4 @@ function regmod_content(&$a) { killme(); } } +} diff --git a/mod/removeme.php b/mod/removeme.php index 904606fd57..6c84c41892 100644 --- a/mod/removeme.php +++ b/mod/removeme.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('removeme_post')) { function removeme_post(&$a) { if(! local_user()) @@ -24,9 +25,10 @@ function removeme_post(&$a) { user_remove($a->user['uid']); // NOTREACHED } - +} } +if(! function_exists('removeme_content')) { function removeme_content(&$a) { if(! local_user()) @@ -50,5 +52,5 @@ function removeme_content(&$a) { )); return $o; - +} } diff --git a/mod/repair_ostatus.php b/mod/repair_ostatus.php index 2b1224f423..e3956ba8cb 100755 --- a/mod/repair_ostatus.php +++ b/mod/repair_ostatus.php @@ -3,6 +3,7 @@ require_once('include/Scrape.php'); require_once('include/follow.php'); +if(! function_exists('repair_ostatus_content')) { function repair_ostatus_content(&$a) { if(! local_user()) { @@ -55,3 +56,4 @@ function repair_ostatus_content(&$a) { return $o; } +} diff --git a/mod/rsd_xml.php b/mod/rsd_xml.php index f4984f0f0f..6f9c209fab 100644 --- a/mod/rsd_xml.php +++ b/mod/rsd_xml.php @@ -1,7 +1,6 @@ <?php - - +if(! function_exists('rsd_xml_content')) { function rsd_xml_content(&$a) { header ("Content-Type: text/xml"); echo '<?xml version="1.0" encoding="UTF-8"?> @@ -21,4 +20,5 @@ function rsd_xml_content(&$a) { </rsd> '; die(); -} \ No newline at end of file +} +} diff --git a/mod/salmon.php b/mod/salmon.php index 9c22e42d11..ee3826d8a8 100644 --- a/mod/salmon.php +++ b/mod/salmon.php @@ -6,6 +6,7 @@ require_once('include/crypto.php'); require_once('include/items.php'); require_once('include/follow.php'); +if(! function_exists('salmon_return')) { function salmon_return($val) { if($val >= 400) @@ -16,9 +17,10 @@ function salmon_return($val) { logger('mod-salmon returns ' . $val); header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err); killme(); - +} } +if(! function_exists('salmon_post')) { function salmon_post(&$a) { $xml = file_get_contents('php://input'); @@ -155,7 +157,7 @@ function salmon_post(&$a) { if(get_pconfig($importer['uid'],'system','ostatus_autofriend')) { $result = new_contact($importer['uid'],$author_link); if($result['success']) { - $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s') + $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s') AND `uid` = %d LIMIT 1", dbesc(NETWORK_OSTATUS), dbesc($author_link), @@ -185,3 +187,4 @@ function salmon_post(&$a) { http_status_exit(200); } +} diff --git a/mod/search.php b/mod/search.php index 7c78339c70..431bd821d6 100644 --- a/mod/search.php +++ b/mod/search.php @@ -4,6 +4,7 @@ require_once('include/security.php'); require_once('include/conversation.php'); require_once('mod/dirfind.php'); +if(! function_exists('search_saved_searches')) { function search_saved_searches() { $o = ''; @@ -39,10 +40,10 @@ function search_saved_searches() { } return $o; - +} } - +if(! function_exists('search_init')) { function search_init(&$a) { $search = ((x($_GET,'search')) ? notags(trim(rawurldecode($_GET['search']))) : ''); @@ -76,17 +77,18 @@ function search_init(&$a) { } - +} } - +if(! function_exists('search_post')) { function search_post(&$a) { if(x($_POST,'search')) $a->data['search'] = $_POST['search']; } +} - +if(! function_exists('search_content')) { function search_content(&$a) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -248,4 +250,4 @@ function search_content(&$a) { return $o; } - +} diff --git a/mod/session.php b/mod/session.php index 22c855edba..ac3d885b63 100644 --- a/mod/session.php +++ b/mod/session.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('session_content')) { function session_content(&$a) { - +} } diff --git a/mod/settings.php b/mod/settings.php index 3efdbf6bde..1b62499c22 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -1,7 +1,7 @@ <?php - -function get_theme_config_file($theme){ +if(! function_exists('get_theme_config_file')) { +function get_theme_config_file($theme) { $a = get_app(); $base_theme = $a->theme_info['extends']; @@ -13,7 +13,9 @@ function get_theme_config_file($theme){ } return null; } +} +if(! function_exists('settings_init')) { function settings_init(&$a) { if(! local_user()) { @@ -110,10 +112,10 @@ function settings_init(&$a) { '$class' => 'settings-widget', '$items' => $tabs, )); - +} } - +if(! function_exists('settings_post')) { function settings_post(&$a) { if(! local_user()) @@ -630,8 +632,9 @@ function settings_post(&$a) { goaway($a->get_baseurl(true) . '/settings' ); return; // NOTREACHED } +} - +if(! function_exists('settings_content')) { function settings_content(&$a) { $o = ''; @@ -1295,6 +1298,5 @@ function settings_content(&$a) { $o .= '</form>' . "\r\n"; return $o; - } - +} diff --git a/mod/share.php b/mod/share.php index 085da4e30d..f3a221eb8e 100644 --- a/mod/share.php +++ b/mod/share.php @@ -1,12 +1,14 @@ <?php + +if(! function_exists('share_init')) { function share_init(&$a) { $post_id = (($a->argc > 1) ? intval($a->argv[1]) : 0); if((! $post_id) || (! local_user())) killme(); - $r = q("SELECT item.*, contact.network FROM `item` - inner join contact on `item`.`contact-id` = `contact`.`id` + $r = q("SELECT item.*, contact.network FROM `item` + inner join contact on `item`.`contact-id` = `contact`.`id` WHERE `item`.`id` = %d AND `item`.`uid` = %d LIMIT 1", intval($post_id), @@ -40,7 +42,9 @@ function share_init(&$a) { echo $o; killme(); } +} +if(! function_exists('share_header')) { function share_header($author, $profile, $avatar, $guid, $posted, $link) { $header = "[share author='".str_replace(array("'", "[", "]"), array("'", "[", "]"),$author). "' profile='".str_replace(array("'", "[", "]"), array("'", "[", "]"),$profile). @@ -56,3 +60,4 @@ function share_header($author, $profile, $avatar, $guid, $posted, $link) { return $header; } +} diff --git a/mod/smilies.php b/mod/smilies.php index c47f95da76..4d498b6746 100644 --- a/mod/smilies.php +++ b/mod/smilies.php @@ -1,3 +1,7 @@ <?php -function smilies_content(&$a) { return smilies('',true); } +if(! function_exists('smilies_content')) { +function smilies_content(&$a) { + return smilies('',true); +} +} diff --git a/mod/starred.php b/mod/starred.php index 2a89ac768b..b4cc326787 100644 --- a/mod/starred.php +++ b/mod/starred.php @@ -1,6 +1,6 @@ <?php - +if(! function_exists('starred_init')) { function starred_init(&$a) { require_once("include/threads.php"); @@ -47,3 +47,4 @@ function starred_init(&$a) { echo json_encode($starred); killme(); } +} diff --git a/mod/statistics_json.php b/mod/statistics_json.php index 21a9a0521c..98cc708d26 100644 --- a/mod/statistics_json.php +++ b/mod/statistics_json.php @@ -5,6 +5,7 @@ require_once("include/plugin.php"); +if(! function_exists('statistics_json_init')) { function statistics_json_init(&$a) { if (!get_config("system", "nodeinfo")) { @@ -57,3 +58,4 @@ function statistics_json_init(&$a) { logger("statistics_init: printed ".print_r($statistics, true), LOGGER_DATA); killme(); } +} diff --git a/mod/subthread.php b/mod/subthread.php index 1486a33b42..6cbaa1d2a7 100644 --- a/mod/subthread.php +++ b/mod/subthread.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); - +if(! function_exists('subthread_content')) { function subthread_content(&$a) { if(! local_user() && ! remote_user()) { @@ -47,7 +47,7 @@ function subthread_content(&$a) { $remote_owner = $r[0]; } - // this represents the post owner on this system. + // this represents the post owner on this system. $r = q("SELECT `contact`.*, `user`.`nickname` FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid` WHERE `contact`.`self` = 1 AND `contact`.`uid` = %d LIMIT 1", @@ -103,7 +103,7 @@ EOT; $bodyverb = t('%1$s is following %2$s\'s %3$s'); if(! isset($bodyverb)) - return; + return; $arr = array(); @@ -123,7 +123,7 @@ EOT; $arr['author-name'] = $contact['name']; $arr['author-link'] = $contact['url']; $arr['author-avatar'] = $contact['thumb']; - + $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; $plink = '[url=' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . ']' . $post_type . '[/url]'; @@ -154,7 +154,5 @@ EOT; call_hooks('post_local_end', $arr); killme(); - } - - +} diff --git a/mod/suggest.php b/mod/suggest.php index b73c2cd1b6..8f5f4f6a12 100644 --- a/mod/suggest.php +++ b/mod/suggest.php @@ -3,7 +3,7 @@ require_once('include/socgraph.php'); require_once('include/contact_widgets.php'); - +if(! function_exists('suggest_init')) { function suggest_init(&$a) { if(! local_user()) return; @@ -42,13 +42,13 @@ function suggest_init(&$a) { ); } } - +} } - +if(! function_exists('suggest_content')) { function suggest_content(&$a) { require_once("mod/proxy.php"); @@ -110,8 +110,9 @@ function suggest_content(&$a) { $o .= replace_macros($tpl,array( '$title' => t('Friend Suggestions'), '$contacts' => $entries, - + )); return $o; } +} diff --git a/mod/tagger.php b/mod/tagger.php index 2c469a58bb..bee37015ea 100644 --- a/mod/tagger.php +++ b/mod/tagger.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); - +if(! function_exists('tagger_content')) { function tagger_content(&$a) { if(! local_user() && ! remote_user()) { @@ -95,7 +95,7 @@ EOT; $bodyverb = t('%1$s tagged %2$s\'s %3$s with %4$s'); if(! isset($bodyverb)) - return; + return; $termlink = html_entity_decode('⌗') . '[url=' . $a->get_baseurl() . '/search?tag=' . urlencode($term) . ']'. $term . '[/url]'; @@ -115,7 +115,7 @@ EOT; $arr['author-name'] = $contact['name']; $arr['author-link'] = $contact['url']; $arr['author-avatar'] = $contact['thumb']; - + $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; $plink = '[url=' . $item['plink'] . ']' . $post_type . '[/url]'; @@ -216,5 +216,5 @@ EOT; return; // NOTREACHED - +} } diff --git a/mod/tagrm.php b/mod/tagrm.php index 176986bc38..70b3ef048f 100644 --- a/mod/tagrm.php +++ b/mod/tagrm.php @@ -2,6 +2,7 @@ require_once('include/bbcode.php'); +if(! function_exists('tagrm_post')) { function tagrm_post(&$a) { if(! local_user()) @@ -40,13 +41,13 @@ function tagrm_post(&$a) { info( t('Tag removed') . EOL ); goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); - - // NOTREACHED + // NOTREACHED +} } - +if(! function_exists('tagrm_content')) { function tagrm_content(&$a) { $o = ''; @@ -95,5 +96,5 @@ function tagrm_content(&$a) { $o .= '</form>'; return $o; - +} } diff --git a/mod/toggle_mobile.php b/mod/toggle_mobile.php index 00991e44ca..dbf0996bba 100644 --- a/mod/toggle_mobile.php +++ b/mod/toggle_mobile.php @@ -1,5 +1,6 @@ <?php +if(! function_exists('toggle_mobile_init')) { function toggle_mobile_init(&$a) { if(isset($_GET['off'])) @@ -14,4 +15,4 @@ function toggle_mobile_init(&$a) { goaway($address); } - +} diff --git a/mod/uexport.php b/mod/uexport.php index a44620a976..eacf300f3f 100644 --- a/mod/uexport.php +++ b/mod/uexport.php @@ -1,6 +1,7 @@ <?php -function uexport_init(&$a){ +if(! function_exists('uexport_init')) { +function uexport_init(&$a) { if(! local_user()) killme(); @@ -55,8 +56,10 @@ function uexport_init(&$a){ )); */ } +} -function uexport_content(&$a){ +if(! function_exists('uexport_content')) { +function uexport_content(&$a) { if ($a->argc > 1) { header("Content-type: application/json"); @@ -86,9 +89,10 @@ function uexport_content(&$a){ '$options' => $options )); - +} } +if(! function_exists('_uexport_multirow')) { function _uexport_multirow($query) { $result = array(); $r = q($query); @@ -103,7 +107,9 @@ function _uexport_multirow($query) { } return $result; } +} +if(! function_exists('_uexport_row')) { function _uexport_row($query) { $result = array(); $r = q($query); @@ -115,9 +121,10 @@ function _uexport_row($query) { } return $result; } +} - -function uexport_account($a){ +if(! function_exists('uexport_account')) { +function uexport_account($a) { $user = _uexport_row( sprintf( "SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user()) ) @@ -153,9 +160,9 @@ function uexport_account($a){ 'version' => FRIENDICA_VERSION, 'schema' => DB_UPDATE_VERSION, 'baseurl' => $a->get_baseurl(), - 'user' => $user, - 'contact' => $contact, - 'profile' => $profile, + 'user' => $user, + 'contact' => $contact, + 'profile' => $profile, 'photo' => $photo, 'pconfig' => $pconfig, 'group' => $group, @@ -164,14 +171,15 @@ function uexport_account($a){ //echo "<pre>"; var_dump(json_encode($output)); killme(); echo json_encode($output); - +} } /** * echoes account data and items as separated json, one per line */ +if(! function_exists('uexport_all')) { function uexport_all(&$a) { - + uexport_account($a); echo "\n"; @@ -199,5 +207,5 @@ function uexport_all(&$a) { $output = array('item' => $r); echo json_encode($output)."\n"; } - +} } diff --git a/mod/uimport.php b/mod/uimport.php index 7ed5648d9e..942268b0ef 100644 --- a/mod/uimport.php +++ b/mod/uimport.php @@ -5,6 +5,7 @@ require_once("include/uimport.php"); +if(! function_exists('uimport_post')) { function uimport_post(&$a) { switch($a->config['register_policy']) { case REGISTER_OPEN: @@ -27,16 +28,18 @@ function uimport_post(&$a) { $verified = 0; break; } - + if (x($_FILES,'accountfile')){ /// @TODO Pass $blocked / $verified, send email to admin on REGISTER_APPROVE import_account($a, $_FILES['accountfile']); return; } } +} +if(! function_exists('uimport_content')) { function uimport_content(&$a) { - + if((! local_user()) && ($a->config['register_policy'] == REGISTER_CLOSED)) { notice("Permission denied." . EOL); return; @@ -51,8 +54,8 @@ function uimport_content(&$a) { return; } } - - + + if(x($_SESSION,'theme')) unset($_SESSION['theme']); if(x($_SESSION,'mobile-theme')) @@ -71,3 +74,4 @@ function uimport_content(&$a) { ), )); } +} diff --git a/mod/update_community.php b/mod/update_community.php index 512629b005..396f4234c0 100644 --- a/mod/update_community.php +++ b/mod/update_community.php @@ -4,6 +4,7 @@ require_once('mod/community.php'); +if(! function_exists('update_community_content')) { function update_community_content(&$a) { header("Content-type: text/html"); @@ -29,5 +30,5 @@ function update_community_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); - -} \ No newline at end of file +} +} diff --git a/mod/update_display.php b/mod/update_display.php index 25b0f77926..9400cb39a6 100644 --- a/mod/update_display.php +++ b/mod/update_display.php @@ -5,6 +5,7 @@ require_once('mod/display.php'); require_once('include/group.php'); +if(! function_exists('update_display_content')) { function update_display_content(&$a) { $profile_uid = intval($_GET['p']); @@ -34,5 +35,5 @@ function update_display_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); - +} } diff --git a/mod/update_network.php b/mod/update_network.php index 1bf3746575..b2e7abc90c 100644 --- a/mod/update_network.php +++ b/mod/update_network.php @@ -5,6 +5,7 @@ require_once('mod/network.php'); require_once('include/group.php'); +if(! function_exists('update_network_content')) { function update_network_content(&$a) { $profile_uid = intval($_GET['p']); @@ -37,5 +38,5 @@ function update_network_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); - +} } diff --git a/mod/update_notes.php b/mod/update_notes.php index 6b8fff5115..e1e4f1d795 100644 --- a/mod/update_notes.php +++ b/mod/update_notes.php @@ -9,6 +9,7 @@ require_once('mod/notes.php'); +if(! function_exists('update_notes_content')) { function update_notes_content(&$a) { $profile_uid = intval($_GET['p']); @@ -20,8 +21,8 @@ function update_notes_content(&$a) { /** * - * Grab the page inner contents by calling the content function from the profile module directly, - * but move any image src attributes to another attribute name. This is because + * Grab the page inner contents by calling the content function from the profile module directly, + * but move any image src attributes to another attribute name. This is because * some browsers will prefetch all the images for the page even if we don't need them. * The only ones we need to fetch are those for new page additions, which we'll discover * on the client side and then swap the image back. @@ -52,5 +53,5 @@ function update_notes_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); - -} \ No newline at end of file +} +} diff --git a/mod/update_profile.php b/mod/update_profile.php index 2492a48ee4..93a94ae0d8 100644 --- a/mod/update_profile.php +++ b/mod/update_profile.php @@ -9,6 +9,7 @@ require_once('mod/profile.php'); +if(! function_exists('update_profile_content')) { function update_profile_content(&$a) { $profile_uid = intval($_GET['p']); @@ -24,8 +25,8 @@ function update_profile_content(&$a) { /** * - * Grab the page inner contents by calling the content function from the profile module directly, - * but move any image src attributes to another attribute name. This is because + * Grab the page inner contents by calling the content function from the profile module directly, + * but move any image src attributes to another attribute name. This is because * some browsers will prefetch all the images for the page even if we don't need them. * The only ones we need to fetch are those for new page additions, which we'll discover * on the client side and then swap the image back. @@ -56,5 +57,5 @@ function update_profile_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); - -} \ No newline at end of file +} +} diff --git a/mod/videos.php b/mod/videos.php index bf8d696b60..f9db7b05b1 100644 --- a/mod/videos.php +++ b/mod/videos.php @@ -5,7 +5,7 @@ require_once('include/bbcode.php'); require_once('include/security.php'); require_once('include/redir.php'); - +if(! function_exists('videos_init')) { function videos_init(&$a) { if($a->argc > 1) @@ -102,9 +102,9 @@ function videos_init(&$a) { return; } +} - - +if(! function_exists('videos_post')) { function videos_post(&$a) { $owner_uid = $a->data['user']['uid']; @@ -176,11 +176,11 @@ function videos_post(&$a) { } goaway($a->get_baseurl() . '/videos/' . $a->data['user']['nickname']); - +} } - +if(! function_exists('videos_content')) { function videos_content(&$a) { // URLs (most aren't currently implemented): @@ -407,4 +407,4 @@ function videos_content(&$a) { $o .= paginate($a); return $o; } - +} diff --git a/mod/view.php b/mod/view.php index 15b3733b3f..a270baeaa1 100644 --- a/mod/view.php +++ b/mod/view.php @@ -2,16 +2,18 @@ /** * load view/theme/$current_theme/style.php with friendica contex */ - -function view_init($a){ + +if(! function_exists('view_init')) { +function view_init($a) { header("Content-Type: text/css"); - + if ($a->argc == 4){ $theme = $a->argv[2]; $THEMEPATH = "view/theme/$theme"; if(file_exists("view/theme/$theme/style.php")) require_once("view/theme/$theme/style.php"); } - + killme(); } +} diff --git a/mod/viewcontacts.php b/mod/viewcontacts.php index 04520e0d93..acb51f0cb4 100644 --- a/mod/viewcontacts.php +++ b/mod/viewcontacts.php @@ -2,6 +2,7 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); +if(! function_exists('viewcontacts_init')) { function viewcontacts_init(&$a) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -26,8 +27,9 @@ function viewcontacts_init(&$a) { profile_load($a,$a->argv[1]); } } +} - +if(! function_exists('viewcontacts_content')) { function viewcontacts_content(&$a) { require_once("mod/proxy.php"); @@ -121,3 +123,4 @@ function viewcontacts_content(&$a) { return $o; } +} diff --git a/mod/viewsrc.php b/mod/viewsrc.php index 3fa4eaed53..1203d18fc9 100644 --- a/mod/viewsrc.php +++ b/mod/viewsrc.php @@ -1,6 +1,6 @@ <?php - +if(! function_exists('viewsrc_content')) { function viewsrc_content(&$a) { if(! local_user()) { @@ -16,7 +16,7 @@ function viewsrc_content(&$a) { return; } - $r = q("SELECT `item`.`body` FROM `item` + $r = q("SELECT `item`.`body` FROM `item` WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 AND `item`.`id` = '%s' LIMIT 1", @@ -33,4 +33,4 @@ function viewsrc_content(&$a) { } return $o; } - +} diff --git a/mod/wall_attach.php b/mod/wall_attach.php index 68752a0e1f..20e646cb9a 100644 --- a/mod/wall_attach.php +++ b/mod/wall_attach.php @@ -3,6 +3,7 @@ require_once('include/attach.php'); require_once('include/datetime.php'); +if(! function_exists('wall_attach_post')) { function wall_attach_post(&$a) { $r_json = (x($_GET,'response') && $_GET['response']=='json'); @@ -190,3 +191,4 @@ function wall_attach_post(&$a) { killme(); // NOTREACHED } +} diff --git a/mod/wall_upload.php b/mod/wall_upload.php index b815348c70..2851807d57 100644 --- a/mod/wall_upload.php +++ b/mod/wall_upload.php @@ -2,6 +2,7 @@ require_once('include/Photo.php'); +if(! function_exists('wall_upload_post')) { function wall_upload_post(&$a, $desktopmode = true) { logger("wall upload: starting new upload", LOGGER_DEBUG); @@ -297,3 +298,4 @@ function wall_upload_post(&$a, $desktopmode = true) { killme(); // NOTREACHED } +} diff --git a/mod/wallmessage.php b/mod/wallmessage.php index b8859badd3..a01dfd2b9f 100644 --- a/mod/wallmessage.php +++ b/mod/wallmessage.php @@ -2,6 +2,7 @@ require_once('include/message.php'); +if(! function_exists('wallmessage_post')) { function wallmessage_post(&$a) { $replyto = get_my_url(); @@ -48,7 +49,7 @@ function wallmessage_post(&$a) { $body = str_replace("\r\n","\n",$body); $body = str_replace("\n\n","\n",$body); - + $ret = send_wallmessage($user, $body, $subject, $replyto); switch($ret){ @@ -69,10 +70,10 @@ function wallmessage_post(&$a) { } // goaway($a->get_baseurl() . '/profile/' . $user['nickname']); - +} } - +if(! function_exists('wallmessage_content')) { function wallmessage_content(&$a) { if(! get_my_url()) { @@ -134,9 +135,9 @@ function wallmessage_content(&$a) { '$nickname' => $user['nickname'], '$linkurl' => t('Please enter a link URL:') )); - - + + $tpl = get_markup_template('wallmessage.tpl'); $o .= replace_macros($tpl,array( '$header' => t('Send Private Message'), @@ -158,3 +159,4 @@ function wallmessage_content(&$a) { return $o; } +} diff --git a/mod/webfinger.php b/mod/webfinger.php index 74bd2c9543..4024671b02 100644 --- a/mod/webfinger.php +++ b/mod/webfinger.php @@ -1,14 +1,13 @@ <?php - - +if(! function_exists('webfinger_content')) { function webfinger_content(&$a) { $o .= '<h3>Webfinger Diagnostic</h3>'; $o .= '<form action="webfinger" method="get">'; $o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] .'" />'; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; @@ -24,3 +23,4 @@ function webfinger_content(&$a) { } return $o; } +} diff --git a/mod/xrd.php b/mod/xrd.php index c23119145c..f8e0a9c409 100644 --- a/mod/xrd.php +++ b/mod/xrd.php @@ -2,6 +2,7 @@ require_once('include/crypto.php'); +if(! function_exists('xrd_init')) { function xrd_init(&$a) { $uri = urldecode(notags(trim($_GET['uri']))); @@ -77,5 +78,5 @@ function xrd_init(&$a) { echo $arr['xml']; killme(); - +} } From 9e3f849e89ce1fdbd195660878f1b10cdeea8248 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 5 Feb 2016 22:12:54 +0100 Subject: [PATCH 048/273] Added documentation --- include/dfrn.php | 72 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index e286b75cce..d62d1d57fb 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -1278,6 +1278,15 @@ class dfrn { return($author); } + /** + * @brief Transforms activity objects into an XML string + * + * @param object $xpath XPath object + * @param object $activity Activity object + * @param text $element element name + * + * @return string XML string + */ private function transform_activity($xpath, $activity, $element) { if (!is_object($activity)) return ""; @@ -1315,6 +1324,13 @@ class dfrn { return($objxml); } + /** + * @brief Processes the mail elements + * + * @param object $xpath XPath object + * @param object $mail mail elements + * @param array $importer Record of the importer user mixed with contact of the content + */ private function process_mail($xpath, $mail, $importer) { logger("Processing mails"); @@ -1359,6 +1375,13 @@ class dfrn { logger("Mail is processed, notification was sent."); } + /** + * @brief Processes the suggestion elements + * + * @param object $xpath XPath object + * @param object $suggestion suggestion elements + * @param array $importer Record of the importer user mixed with contact of the content + */ private function process_suggestion($xpath, $suggestion, $importer) { logger("Processing suggestions"); @@ -1453,6 +1476,13 @@ class dfrn { } + /** + * @brief Processes the relocation elements + * + * @param object $xpath XPath object + * @param object $relocation relocation elements + * @param array $importer Record of the importer user mixed with contact of the content + */ private function process_relocation($xpath, $relocation, $importer) { logger("Processing relocations"); @@ -1534,6 +1564,14 @@ class dfrn { return true; } + /** + * @brief Updates an item + * + * @param array $current the current item record + * @param array $item the new item record + * @param array $importer Record of the importer user mixed with contact of the content + * @param int $entrytype Is it a toplevel entry, a comment or a relayed comment? + */ private function update_content($current, $item, $importer, $entrytype) { $changed = false; @@ -1578,6 +1616,14 @@ class dfrn { return $changed; } + /** + * @brief Detects the entry type of the item + * + * @param array $importer Record of the importer user mixed with contact of the content + * @param array $item the new item record + * + * @return int Is it a toplevel entry, a comment or a relayed comment? + */ private function get_entry_type($importer, $item) { if ($item["parent-uri"] != $item["uri"]) { $community = false; @@ -1637,6 +1683,13 @@ class dfrn { } + /** + * @brief Send a "poke" + * + * @param array $item the new item record + * @param array $importer Record of the importer user mixed with contact of the content + * @param int $posted_id The record number of item record that was just posted + */ private function do_poke($item, $importer, $posted_id) { $verb = urldecode(substr($item["verb"],strpos($item["verb"], "#")+1)); if(!$verb) @@ -1683,6 +1736,14 @@ class dfrn { } } + /** + * @brief Processes the entry elements which contain the items and comments + * + * @param array $header Array of the header elements that always stay the same + * @param object $xpath XPath object + * @param object $entry entry elements + * @param array $importer Record of the importer user mixed with contact of the content + */ private function process_entry($header, $xpath, $entry, $importer) { logger("Processing entries"); @@ -2079,7 +2140,14 @@ class dfrn { } } - private function process_deletion($header, $xpath, $deletion, $importer) { + /** + * @brief Deletes items + * + * @param object $xpath XPath object + * @param object $deletion deletion elements + * @param array $importer Record of the importer user mixed with contact of the content + */ + private function process_deletion($xpath, $deletion, $importer) { logger("Processing deletions"); @@ -2283,7 +2351,7 @@ class dfrn { $deletions = $xpath->query("/atom:feed/at:deleted-entry"); foreach ($deletions AS $deletion) - self::process_deletion($header, $xpath, $deletion, $importer); + self::process_deletion($xpath, $deletion, $importer); if (!$sort_by_date) { $entries = $xpath->query("/atom:feed/atom:entry"); From 0bccfea5968c5195a253bb7f2066145e0f539144 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Sat, 6 Feb 2016 00:54:50 +0100 Subject: [PATCH 049/273] vier: some consistency fixes for the dark scheme --- view/theme/vier/dark.css | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/view/theme/vier/dark.css b/view/theme/vier/dark.css index 8e128ae27f..d34330010e 100644 --- a/view/theme/vier/dark.css +++ b/view/theme/vier/dark.css @@ -35,6 +35,11 @@ body, section, blockquote, blockquote.shared_content, #profile-jot-form, background-color: #171B1F !important; } +#profile-jot-acl-wrapper, #event-notice, #event-wrapper, +#cboxLoadedContent, .contact-photo-menu { + background-color: #252C33 !important; +} + div.rte, .mceContentBody { background:none repeat scroll 0 0 #333333!important; color:#FFF!important; @@ -63,3 +68,31 @@ li :hover { #viewcontact_wrapper-network { background-color: #343434; } + +/* ACL permission popup */ + .acl-list-item.groupshow { + border-color: #9ade00 !important; +} + +.acl-list-item.grouphide { + border-color: #ff4141 !important; +} + +/* Notifications */ +li.notify-unseen { + background-color: #252C33; +} + +li.notify-seen { + background-color: #1C2126; +} + +.photo-top-album-name { + background-color: #252C33; +} + +#panel { + background-color: #252C33; + border: 3px solid #364e59; + color: #989898; +} From 4896517385f3b4484564bf675a5342f74d70f48a Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Sat, 6 Feb 2016 01:08:10 +0100 Subject: [PATCH 050/273] datetime.php: little more docu --- include/datetime.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/datetime.php b/include/datetime.php index 3a75690d22..6983b6431d 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -106,7 +106,7 @@ function field_timezone($name='timezone', $label='', $current = 'America/Los_Ang * @param string $fmt Output format recognised from php's DateTime class * http://www.php.net/manual/en/datetime.format.php * - * @return string + * @return string Formatted date according to given format */ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d H:i:s") { @@ -153,6 +153,7 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d } $d->setTimeZone($to_obj); + return($d->format($fmt)); } @@ -380,7 +381,7 @@ function relative_date($posted_date,$format = null) { * @param string $owner_tz (optional) Timezone of the person of interest * @param string $viewer_tz (optional) Timezone of the person viewing * - * @return int + * @return int Age in years */ function age($dob,$owner_tz = '',$viewer_tz = '') { if(! intval($dob)) From bd3e10b132ae95aaa34eb7ba6f618f5d9ac2612c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 6 Feb 2016 10:16:16 +0100 Subject: [PATCH 051/273] Bugfix in the poke function / added documentation. --- include/dfrn.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index d62d1d57fb..eaf4341f03 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -1270,6 +1270,10 @@ class dfrn { update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"], (strtotime($contact["avatar-date"]) > strtotime($r[0]["avatar-date"]))); + // The generation is a sign for the reliability of the provided data. + // It is used in the socgraph.php to prevent that old contact data + // that was relayed over several servers can overwrite contact + // data that we received directly. $contact["generation"] = 2; $contact["photo"] = $author["avatar"]; update_gcontact($contact); @@ -1711,7 +1715,7 @@ class dfrn { break; } } - if($Blink && link_compare($Blink,$a->get_baseurl()."/profile/".$importer["nickname"])) { + if($Blink && link_compare($Blink,App::get_baseurl()."/profile/".$importer["nickname"])) { // send a notification notification(array( @@ -1722,7 +1726,7 @@ class dfrn { "to_email" => $importer["email"], "uid" => $importer["importer_uid"], "item" => $item, - "link" => $a->get_baseurl()."/display/".urlencode(get_item_guid($posted_id)), + "link" => App::get_baseurl()."/display/".urlencode(get_item_guid($posted_id)), "source_name" => stripslashes($item["author-name"]), "source_link" => $item["author-link"], "source_photo" => ((link_compare($item["author-link"],$importer["url"])) From 3890415767aa687cf7a2e566e0f07b9c663f3de1 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 6 Feb 2016 11:16:00 +0100 Subject: [PATCH 052/273] Receiving pokes work now --- include/dfrn.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index eaf4341f03..90370694ec 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -1703,9 +1703,7 @@ class dfrn { if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { // somebody was poked/prodded. Was it me? - $links = parse_xml_string("<links>".unxmlify($xo->link)."</links>",false); - - foreach($links->link as $l) { + foreach($xo->link as $l) { $atts = $l->attributes(); switch($atts["rel"]) { case "alternate": @@ -1715,6 +1713,7 @@ class dfrn { break; } } + if($Blink && link_compare($Blink,App::get_baseurl()."/profile/".$importer["nickname"])) { // send a notification From 8ea4659031c28256332c69ae6ec88559b225b5b1 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 6 Feb 2016 17:48:03 +0100 Subject: [PATCH 053/273] The code was rearranged to improve readability --- include/dfrn.php | 206 ++++++++++++++++++++++++++--------------------- 1 file changed, 113 insertions(+), 93 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 90370694ec..043f02cc2f 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -1739,6 +1739,107 @@ class dfrn { } } + /** + * @brief Processes several actions, depending on the verb + * + * @param int $entrytype Is it a toplevel entry, a comment or a relayed comment? + * @param array $importer Record of the importer user mixed with contact of the content + * @param array $item the new item record + * @param bool $is_like Is the verb a "like"? + * + * @return bool Should the processing of the entries be continued? + */ + private function process_verbs($entrytype, $importer, &$item, &$is_like) { + if (($entrytype == DFRN_TOP_LEVEL)) { + // The filling of the the "contact" variable is done for legcy reasons + // The functions below are partly used by ostatus.php as well - where we have this variable + $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"])); + $contact = $r[0]; + $nickname = $contact["nick"]; + + // Big question: Do we need these functions? They were part of the "consume_feed" function. + // This function once was responsible for DFRN and OStatus. + if(activity_match($item["verb"],ACTIVITY_FOLLOW)) { + logger("New follower"); + new_follower($importer, $contact, $item, $nickname); + return false; + } + if(activity_match($item["verb"],ACTIVITY_UNFOLLOW)) { + logger("Lost follower"); + lose_follower($importer, $contact, $item); + return false; + } + if(activity_match($item["verb"],ACTIVITY_REQ_FRIEND)) { + logger("New friend request"); + new_follower($importer, $contact, $item, $nickname, true); + return false; + } + if(activity_match($item["verb"],ACTIVITY_UNFRIEND)) { + logger("Lost sharer"); + lose_sharer($importer, $contact, $item); + return false; + } + } else { + if(($item["verb"] === ACTIVITY_LIKE) + || ($item["verb"] === ACTIVITY_DISLIKE) + || ($item["verb"] === ACTIVITY_ATTEND) + || ($item["verb"] === ACTIVITY_ATTENDNO) + || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) { + $is_like = true; + $item["type"] = "activity"; + $item["gravity"] = GRAVITY_LIKE; + // only one like or dislike per person + // splitted into two queries for performance issues + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) + ); + if($r && count($r)) + return false; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) + ); + if($r && count($r)) + return false; + } else + $is_like = false; + + if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + + $xo = parse_xml_string($item["object"],false); + $xt = parse_xml_string($item["target"],false); + + if($xt->type == ACTIVITY_OBJ_NOTE) { + $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($xt->id), + intval($importer["importer_uid"]) + ); + + if(!count($r)) + return false; + + // extract tag, if not duplicate, add to parent item + if($xo->content) { + if(!(stristr($r[0]["tag"],trim($xo->content)))) { + q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", + dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), + intval($r[0]["id"]) + ); + create_tags_from_item($r[0]["id"]); + } + } + } + } + } + return true; + } + /** * @brief Processes the entry elements which contain the items and comments * @@ -1932,6 +2033,11 @@ class dfrn { if (($item["network"] != $author["network"]) AND ($author["network"] != "")) $item["network"] = $author["network"]; + + if($importer["rel"] == CONTACT_IS_FOLLOWER) { + logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); + return; + } } if ($entrytype == DFRN_REPLY_RC) { @@ -1941,6 +2047,7 @@ class dfrn { if (!isset($item["object-type"])) $item["object-type"] = ACTIVITY_OBJ_NOTE; + // Is it an event? if ($item["object-type"] == ACTIVITY_OBJ_EVENT) { logger("Item ".$item["uri"]." seems to contain an event.", LOGGER_DEBUG); $ev = bbtoevent($item["body"]); @@ -1965,35 +2072,6 @@ class dfrn { return; } } - - // The filling of the the "contact" variable is done for legcy reasons - // The functions below are partly used by ostatus.php as well - where we have this variable - $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"])); - $contact = $r[0]; - $nickname = $contact["nick"]; - - // Big question: Do we need these functions? They were part of the "consume_feed" function. - // This function once was responsible for DFRN and OStatus. - if(activity_match($item['verb'],ACTIVITY_FOLLOW)) { - logger('New follower'); - new_follower($importer, $contact, $item, $nickname); - return; - } - if(activity_match($item['verb'],ACTIVITY_UNFOLLOW)) { - logger('Lost follower'); - lose_follower($importer, $contact, $item); - return; - } - if(activity_match($item['verb'],ACTIVITY_REQ_FRIEND)) { - logger('New friend request'); - new_follower($importer, $contact, $item, $nickname, true); - return; - } - if(activity_match($item['verb'],ACTIVITY_UNFRIEND)) { - logger('Lost sharer'); - lose_sharer($importer, $contact, $item); - return; - } } $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", @@ -2001,6 +2079,11 @@ class dfrn { intval($importer["importer_uid"]) ); + if (!self::process_verbs($entrytype, $importer, $item, $is_like)) { + logger("Exiting because 'process_verbs' told us so", LOGGER_DEBUG); + return; + } + // Update content if 'updated' changes if(count($r)) { if (self::update_content($r[0], $item, $importer, $entrytype)) @@ -2011,69 +2094,6 @@ class dfrn { } if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { - if($importer["rel"] == CONTACT_IS_FOLLOWER) { - logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); - return; - } - - if(($item["verb"] === ACTIVITY_LIKE) - || ($item["verb"] === ACTIVITY_DISLIKE) - || ($item["verb"] === ACTIVITY_ATTEND) - || ($item["verb"] === ACTIVITY_ATTENDNO) - || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) { - $is_like = true; - $item["type"] = "activity"; - $item["gravity"] = GRAVITY_LIKE; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($item["uid"]), - dbesc($item["author-link"]), - dbesc($item["verb"]), - dbesc($item["parent-uri"]) - ); - if($r && count($r)) - return; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($item["uid"]), - dbesc($item["author-link"]), - dbesc($item["verb"]), - dbesc($item["parent-uri"]) - ); - if($r && count($r)) - return; - - } else - $is_like = false; - - if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($item["object"],false); - $xt = parse_xml_string($item["target"],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($xt->id), - intval($importer["importer_uid"]) - ); - - if(!count($r)) - return; - - // extract tag, if not duplicate, add to parent item - if($xo->content) { - if(!(stristr($r[0]["tag"],trim($xo->content)))) { - q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", - dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), - intval($r[0]["id"]) - ); - create_tags_from_item($r[0]["id"]); - } - } - } - } - $posted_id = item_store($item); $parent = 0; @@ -2113,7 +2133,7 @@ class dfrn { return true; } - } else { + } else { // $entrytype == DFRN_TOP_LEVEL if(!link_compare($item["owner-link"],$importer["url"])) { // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, // but otherwise there's a possible data mixup on the sender's system. From af219ac9ec547595fbc2b5b19ac402041f35dcf5 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 6 Feb 2016 21:44:10 +0100 Subject: [PATCH 054/273] Just some more code cleanup and documentation. --- include/dfrn.php | 83 +++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 043f02cc2f..84660a0d18 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -26,9 +26,9 @@ require_once("library/HTMLPurifier.auto.php"); */ class dfrn { - const DFRN_TOP_LEVEL = 0; - const DFRN_REPLY = 1; - const DFRN_REPLY_RC = 2; + const DFRN_TOP_LEVEL = 0; // Top level posting + const DFRN_REPLY = 1; // Regular reply that is stored locally + const DFRN_REPLY_RC = 2; // Reply that will be relayed /** * @brief Generates the atom entries for delivery.php @@ -1840,6 +1840,47 @@ class dfrn { return true; } + /** + * @brief Processes the link elements + * + * @param object $links link elements + * @param array $item the item record + */ + private function parse_links($links, &$item) { + $rel = ""; + $href = ""; + $type = ""; + $length = "0"; + $title = ""; + foreach ($links AS $link) { + foreach($link->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "rel") + $rel = $attributes->textContent; + if ($attributes->name == "type") + $type = $attributes->textContent; + if ($attributes->name == "length") + $length = $attributes->textContent; + if ($attributes->name == "title") + $title = $attributes->textContent; + } + if (($rel != "") AND ($href != "")) + switch($rel) { + case "alternate": + $item["plink"] = $href; + break; + case "enclosure": + $enclosure = $href; + if(strlen($item["attach"])) + $item["attach"] .= ","; + + $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; + break; + } + } + } + /** * @brief Processes the entry elements which contain the items and comments * @@ -1970,40 +2011,8 @@ class dfrn { $enclosure = ""; $links = $xpath->query("atom:link", $entry); - if ($links) { - $rel = ""; - $href = ""; - $type = ""; - $length = "0"; - $title = ""; - foreach ($links AS $link) { - foreach($link->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "rel") - $rel = $attributes->textContent; - if ($attributes->name == "type") - $type = $attributes->textContent; - if ($attributes->name == "length") - $length = $attributes->textContent; - if ($attributes->name == "title") - $title = $attributes->textContent; - } - if (($rel != "") AND ($href != "")) - switch($rel) { - case "alternate": - $item["plink"] = $href; - break; - case "enclosure": - $enclosure = $href; - if(strlen($item["attach"])) - $item["attach"] .= ","; - - $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; - break; - } - } - } + if ($links) + self::parse_links($links, $item); // Is it a reply or a top level posting? $item["parent-uri"] = $item["uri"]; From 44592611e1582fd97ae1988343418a0dae1ae2a0 Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Sun, 7 Feb 2016 14:27:13 +0100 Subject: [PATCH 055/273] new api for notifications /api/friendica/notification returns first 50 notifications for current user /api/friendica¬ification/<id> set note <id> as seen and return item object if possible new class NotificationsManager to query for notifications and set seen state --- include/NotificationsManager.php | 113 +++++++++++++++++++++++++++++++ include/api.php | 79 +++++++++++++++++++-- mod/notify.php | 82 ++++++++++------------ mod/ping.php | 4 +- 4 files changed, 223 insertions(+), 55 deletions(-) create mode 100644 include/NotificationsManager.php diff --git a/include/NotificationsManager.php b/include/NotificationsManager.php new file mode 100644 index 0000000000..8b0ca9e13d --- /dev/null +++ b/include/NotificationsManager.php @@ -0,0 +1,113 @@ +<?php +require_once("include/datetime.php"); + +class NotificationsManager { + private $a; + + public function __construct() { + $this->a = get_app(); + } + + private function _set_extra($notes) { + $rets = array(); + foreach($notes as $n) { + $local_time = datetime_convert('UTC',date_default_timezone_get(),$n['date']); + $n['timestamp'] = strtotime($local_time); + $n['date_rel'] = relative_date($n['date']); + $rets[] = $n; + } + return $rets; + } + + + /** + * @brief get all notifications for local_user() + * + * @param array $filter optional Array "column name"=>value: filter query by columns values + * @param string $order optional Space separated list of column to sort by. prepend name with "+" to sort ASC, "-" to sort DESC. Default to "-date" + * @param string $limit optional Query limits + * + * @return array of results or false on errors + */ + public function getAll($filter = array(), $order="-date", $limit="") { + $filter_str = array(); + $filter_sql = ""; + foreach($filter as $column => $value) { + $filter_str[] = sprintf("`%s` = '%s'", $column, dbesc($value)); + } + if (count($filter_str)>0) { + $filter_sql = "AND ".implode(" AND ", $filter_str); + } + + $aOrder = explode(" ", $order); + $asOrder = array(); + foreach($aOrder as $o) { + $dir = "asc"; + if ($o[0]==="-") { + $dir = "desc"; + $o = substr($o,1); + } + if ($o[0]==="+") { + $dir = "asc"; + $o = substr($o,1); + } + $asOrder[] = "$o $dir"; + } + $order_sql = implode(", ", $asOrder); + + if ($limit!="") $limit = " LIMIT ".$limit; + + $r = q("SELECT * from notify where uid = %d $filter_sql order by $order_sql $limit", + intval(local_user()) + ); + if ($r!==false && count($r)>0) return $this->_set_extra($r); + return false; + } + + /** + * @brief get one note for local_user() by $id value + * + * @param int $id + * @return array note values or null if not found + */ + public function getByID($id) { + $r = q("select * from notify where id = %d and uid = %d limit 1", + intval($id), + intval(local_user()) + ); + if($r!==false && count($r)>0) { + return $this->_set_extra($r)[0]; + } + return null; + } + + /** + * @brief set seen state of $note of local_user() + * + * @param array $note + * @param bool $seen optional true or false, default true + * @return bool true on success, false on errors + */ + public function setSeen($note, $seen = true) { + return q("update notify set seen = %d where ( link = '%s' or ( parent != 0 and parent = %d and otype = '%s' )) and uid = %d", + intval($seen), + dbesc($note['link']), + intval($note['parent']), + dbesc($note['otype']), + intval(local_user()) + ); + } + + /** + * @brief set seen state of all notifications of local_user() + * + * @param bool $seen optional true or false. default true + * @return bool true on success, false on error + */ + public function setAllSeen($seen = true) { + return q("update notify set seen = %d where uid = %d", + intval($seen), + intval(local_user()) + ); + } +} diff --git a/include/api.php b/include/api.php index 4d206da28e..5acc816575 100644 --- a/include/api.php +++ b/include/api.php @@ -23,6 +23,7 @@ require_once('include/message.php'); require_once('include/group.php'); require_once('include/like.php'); + require_once('include/NotificationsManager.php'); define('API_METHOD_ANY','*'); @@ -250,7 +251,7 @@ */ function api_call(&$a){ GLOBAL $API, $called_api; - + $type="json"; if (strpos($a->query_string, ".xml")>0) $type="xml"; if (strpos($a->query_string, ".json")>0) $type="json"; @@ -680,6 +681,29 @@ } + /** + * @brief transform $data array in xml without a template + * + * @param array $data + * @return string xml string + */ + function api_array_to_xml($data, $ename="") { + $attrs=""; + $childs=""; + foreach($data as $k=>$v) { + $k=trim($k,'$'); + if (!is_array($v)) { + $attrs .= sprintf('%s="%s" ', $k, $v); + } else { + if (is_numeric($k)) $k=trim($ename,'s'); + $childs.=api_array_to_xml($v, $k); + } + } + $res = $childs; + if ($ename!="") $res = "<$ename $attrs>$res</$ename>"; + return $res; + } + /** * load api $templatename for $type and replace $data array */ @@ -692,13 +716,17 @@ case "rss": case "xml": $data = array_xmlify($data); - $tpl = get_markup_template("api_".$templatename."_".$type.".tpl"); - if(! $tpl) { - header ("Content-Type: text/xml"); - echo '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<status><error>not implemented</error></status>'; - killme(); + if ($templatename==="<auto>") { + $ret = api_array_to_xml($data); + } else { + $tpl = get_markup_template("api_".$templatename."_".$type.".tpl"); + if(! $tpl) { + header ("Content-Type: text/xml"); + echo '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<status><error>not implemented</error></status>'; + killme(); + } + $ret = replace_macros($tpl, $data); } - $ret = replace_macros($tpl, $data); break; case "json": $ret = $data; @@ -3386,6 +3414,43 @@ api_register_func('api/friendica/activity/unattendno', 'api_friendica_activity', true, API_METHOD_POST); api_register_func('api/friendica/activity/unattendmaybe', 'api_friendica_activity', true, API_METHOD_POST); + /** + * returns notifications + * if called with note id set note seen and returns associated item (if possible) + */ + function api_friendica_notification(&$a, $type) { + if (api_user()===false) throw new ForbiddenException(); + + $nm = new NotificationsManager(); + + if ($a->argc==3) { + $notes = $nm->getAll(array(), "+seen -date", 50); + return api_apply_template("<auto>", $type, array('$notes' => $notes)); + } + if ($a->argc==4) { + $note = $nm->getByID(intval($a->argv[3])); + if (is_null($note)) throw new BadRequestException("Invalid argument"); + $nm->setSeen($note); + if ($note['otype']=='item') { + // would be really better with a ItemsManager and $im->getByID() :-P + $r = q("SELECT * FROM item WHERE id=%d AND uid=%d", + intval($note['iid']), + intval(local_user()) + ); + if ($r===false) throw new NotFoundException(); + $user_info = api_get_user($a); + $ret = api_format_items($r,$user_info); + $data = array('$statuses' => $ret); + return api_apply_template("timeline", $type, $data); + } else { + return api_apply_template('test', $type, array('ok' => $ok)); + } + + } + throw new BadRequestException("Invalid argument count"); + } + api_register_func('api/friendica/notification', 'api_friendica_notification', true, API_METHOD_GET); + /* To.Do: [pagename] => api/1.1/statuses/lookup.json diff --git a/mod/notify.php b/mod/notify.php index 7acac1084a..7c367708bb 100644 --- a/mod/notify.php +++ b/mod/notify.php @@ -1,43 +1,34 @@ <?php - +require_once('include/NotificationsManager.php'); if(! function_exists('notify_init')) { function notify_init(&$a) { - if(! local_user()) - return; + if(! local_user()) return; + $nm = new NotificationsManager(); + if($a->argc > 2 && $a->argv[1] === 'view' && intval($a->argv[2])) { - $r = q("select * from notify where id = %d and uid = %d limit 1", - intval($a->argv[2]), - intval(local_user()) - ); - if(count($r)) { - q("update notify set seen = 1 where ( link = '%s' or ( parent != 0 and parent = %d and otype = '%s' )) and uid = %d", - dbesc($r[0]['link']), - intval($r[0]['parent']), - dbesc($r[0]['otype']), - intval(local_user()) - ); - + $note = $nm->getByID($a->argv[2]); + if ($note) { + $nm->setSeen($note); + // The friendica client has problems with the GUID. this is some workaround if ($a->is_friendica_app()) { require_once("include/items.php"); - $urldata = parse_url($r[0]['link']); + $urldata = parse_url($note['link']); $guid = basename($urldata["path"]); $itemdata = get_item_id($guid, local_user()); if ($itemdata["id"] != 0) - $r[0]['link'] = $a->get_baseurl().'/display/'.$itemdata["nick"].'/'.$itemdata["id"]; + $note['link'] = $a->get_baseurl().'/display/'.$itemdata["nick"].'/'.$itemdata["id"]; } - goaway($r[0]['link']); + goaway($note['link']); } goaway($a->get_baseurl(true)); } if($a->argc > 2 && $a->argv[1] === 'mark' && $a->argv[2] === 'all' ) { - $r = q("update notify set seen = 1 where uid = %d", - intval(local_user()) - ); + $r = $nm->setAllSeen(); $j = json_encode(array('result' => ($r) ? 'success' : 'fail')); echo $j; killme(); @@ -47,38 +38,37 @@ function notify_init(&$a) { if(! function_exists('notify_content')) { function notify_content(&$a) { - if(! local_user()) - return login(); + if(! local_user()) return login(); - $notif_tpl = get_markup_template('notifications.tpl'); + $nm = new NotificationsManager(); + + $notif_tpl = get_markup_template('notifications.tpl'); - $not_tpl = get_markup_template('notify.tpl'); - require_once('include/bbcode.php'); + $not_tpl = get_markup_template('notify.tpl'); + require_once('include/bbcode.php'); - $r = q("SELECT * from notify where uid = %d and seen = 0 order by date desc", - intval(local_user()) - ); - - if (count($r) > 0) { - foreach ($r as $it) { - $notif_content .= replace_macros($not_tpl,array( - '$item_link' => $a->get_baseurl(true).'/notify/view/'. $it['id'], - '$item_image' => $it['photo'], - '$item_text' => strip_tags(bbcode($it['msg'])), - '$item_when' => relative_date($it['date']) - )); - } - } else { - $notif_content .= t('No more system notifications.'); + $r = $nm->getAll(array('seen'=>0)); + if ($r!==false && count($r) > 0) { + foreach ($r as $it) { + $notif_content .= replace_macros($not_tpl,array( + '$item_link' => $a->get_baseurl(true).'/notify/view/'. $it['id'], + '$item_image' => $it['photo'], + '$item_text' => strip_tags(bbcode($it['msg'])), + '$item_when' => relative_date($it['date']) + )); } + } else { + $notif_content .= t('No more system notifications.'); + } - $o .= replace_macros($notif_tpl, array( - '$notif_header' => t('System Notifications'), - '$tabs' => '', // $tabs, - '$notif_content' => $notif_content, - )); + $o .= replace_macros($notif_tpl, array( + '$notif_header' => t('System Notifications'), + '$tabs' => false, // $tabs, + '$notif_content' => $notif_content, + )); return $o; } } + diff --git a/mod/ping.php b/mod/ping.php index 0d1659a763..adff0912f4 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -206,8 +206,8 @@ function ping_init(&$a) { $local_time = datetime_convert('UTC',date_default_timezone_get(),$n['date']); call_hooks('ping_xmlize', $n); - $notsxml = '<note href="%s" name="%s" url="%s" photo="%s" date="%s" seen="%s" timestamp="%s" >%s</note>'."\n"; - return sprintf ( $notsxml, + $notsxml = '<note id="%d" href="%s" name="%s" url="%s" photo="%s" date="%s" seen="%s" timestamp="%s" >%s</note>'."\n"; + return sprintf ( $notsxml, intval($n['id']), xmlify($n['href']), xmlify($n['name']), xmlify($n['url']), xmlify($n['photo']), xmlify(relative_date($n['date'])), xmlify($n['seen']), xmlify(strtotime($local_time)), xmlify($n['message']) From 102c06a41f3fb6308a75a748a347ebc84b7362dd Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Sun, 7 Feb 2016 14:29:07 +0100 Subject: [PATCH 056/273] fix error in template in notifications page --- view/templates/notifications.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/templates/notifications.tpl b/view/templates/notifications.tpl index 0b7e77a07b..54f9de0c7a 100644 --- a/view/templates/notifications.tpl +++ b/view/templates/notifications.tpl @@ -2,7 +2,7 @@ <h1>{{$notif_header}}</h1> -{{include file="common_tabs.tpl"}} +{{if $tabs }}{{include file="common_tabs.tpl"}}{{/if}} <div class="notif-network-wrapper"> {{$notif_content}} From b202e02fbf09859c06ed21cc3b6bec1530ccf1f1 Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Sun, 7 Feb 2016 15:11:34 +0100 Subject: [PATCH 057/273] Revert "Updated modules to allow for partial overrides without errors" This reverts commit db949bb802448184bfe5164d8d3dd86ddf51b187. --- mod/_well_known.php | 4 -- mod/acctlink.php | 3 +- mod/acl.php | 4 +- mod/admin.php | 78 +++++++++++---------------------------- mod/allfriends.php | 2 - mod/amcd.php | 5 +-- mod/api.php | 11 +++--- mod/apps.php | 40 ++++++++++---------- mod/attach.php | 3 +- mod/babel.php | 46 +++++++++++------------ mod/bookmarklet.php | 5 +-- mod/cb.php | 11 +----- mod/common.php | 2 - mod/community.php | 13 +++---- mod/contactgroup.php | 4 +- mod/contacts.php | 38 ++++--------------- mod/content.php | 48 ++++++++++++------------ mod/credits.php | 2 - mod/crepair.php | 10 ++--- mod/delegate.php | 10 ++--- mod/dfrn_confirm.php | 3 +- mod/dfrn_notify.php | 6 +-- mod/dfrn_poll.php | 13 +++---- mod/directory.php | 19 +++++----- mod/dirfind.php | 6 +-- mod/display.php | 9 ++--- mod/editpost.php | 5 ++- mod/events.php | 6 +-- mod/fbrowser.php | 3 +- mod/filer.php | 5 +-- mod/filerm.php | 2 - mod/follow.php | 4 -- mod/friendica.php | 11 +++--- mod/fsuggest.php | 19 +++++----- mod/group.php | 11 ++---- mod/hcard.php | 10 ++--- mod/help.php | 3 +- mod/hostxrd.php | 3 +- mod/ignored.php | 3 +- mod/install.php | 55 +++++++++------------------ mod/invite.php | 13 +++---- mod/item.php | 13 ++----- mod/like.php | 7 ++-- mod/localtime.php | 11 +++--- mod/lockview.php | 20 +++++----- mod/login.php | 4 +- mod/lostpass.php | 7 ++-- mod/maintenance.php | 3 +- mod/manage.php | 8 ++-- mod/match.php | 2 - mod/message.php | 13 ++----- mod/modexp.php | 4 +- mod/mood.php | 10 ++--- mod/msearch.php | 9 ++--- mod/navigation.php | 3 +- mod/network.php | 17 ++------- mod/newmember.php | 10 ++--- mod/nodeinfo.php | 11 ++---- mod/nogroup.php | 6 +-- mod/noscrape.php | 3 +- mod/notes.php | 20 +++++----- mod/notice.php | 9 ++--- mod/notifications.php | 6 +-- mod/notify.php | 8 ++-- mod/oembed.php | 4 +- mod/oexchange.php | 17 +++++---- mod/openid.php | 10 ++--- mod/opensearch.php | 16 ++++---- mod/ostatus_subscribe.php | 2 - mod/p.php | 4 +- mod/parse_url.php | 18 ++------- mod/photo.php | 2 - mod/photos.php | 15 ++++---- mod/ping.php | 4 -- mod/poco.php | 3 +- mod/poke.php | 12 +++--- mod/post.php | 7 ++-- mod/pretheme.php | 4 +- mod/probe.php | 4 +- mod/profile.php | 7 ++-- mod/profile_photo.php | 26 ++++++------- mod/profiles.php | 14 ++----- mod/profperm.php | 12 +++--- mod/proxy.php | 12 ------ mod/pubsub.php | 20 +++++----- mod/pubsubhubbub.php | 5 +-- mod/qsearch.php | 3 +- mod/randprof.php | 3 +- mod/receive.php | 4 +- mod/redir.php | 6 +-- mod/regmod.php | 9 ++--- mod/removeme.php | 6 +-- mod/repair_ostatus.php | 2 - mod/rsd_xml.php | 6 +-- mod/salmon.php | 7 +--- mod/search.php | 14 +++---- mod/session.php | 3 +- mod/settings.php | 16 ++++---- mod/share.php | 9 +---- mod/smilies.php | 6 +-- mod/starred.php | 3 +- mod/statistics_json.php | 2 - mod/subthread.php | 12 +++--- mod/suggest.php | 9 ++--- mod/tagger.php | 8 ++-- mod/tagrm.php | 9 ++--- mod/toggle_mobile.php | 3 +- mod/uexport.php | 30 ++++++--------- mod/uimport.php | 12 ++---- mod/update_community.php | 5 +-- mod/update_display.php | 3 +- mod/update_network.php | 3 +- mod/update_notes.php | 9 ++--- mod/update_profile.php | 9 ++--- mod/videos.php | 12 +++--- mod/view.php | 10 ++--- mod/viewcontacts.php | 5 +-- mod/viewsrc.php | 6 +-- mod/wall_attach.php | 2 - mod/wall_upload.php | 2 - mod/wallmessage.php | 12 +++--- mod/webfinger.php | 6 +-- mod/xrd.php | 3 +- 123 files changed, 471 insertions(+), 768 deletions(-) diff --git a/mod/_well_known.php b/mod/_well_known.php index 6c33136f95..33070a1ecd 100644 --- a/mod/_well_known.php +++ b/mod/_well_known.php @@ -2,7 +2,6 @@ require_once("mod/hostxrd.php"); require_once("mod/nodeinfo.php"); -if(! function_exists('_well_known_init')) { function _well_known_init(&$a){ if ($a->argc > 1) { switch($a->argv[1]) { @@ -20,9 +19,7 @@ function _well_known_init(&$a){ http_status_exit(404); killme(); } -} -if(! function_exists('wk_social_relay')) { function wk_social_relay(&$a) { define('SR_SCOPE_ALL', 'all'); @@ -67,4 +64,3 @@ function wk_social_relay(&$a) { echo json_encode($relay, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); exit; } -} diff --git a/mod/acctlink.php b/mod/acctlink.php index a551e3dbd6..a2365803ac 100644 --- a/mod/acctlink.php +++ b/mod/acctlink.php @@ -2,8 +2,8 @@ require_once('include/Scrape.php'); -if(! function_exists('acctlink_init')) { function acctlink_init(&$a) { + if(x($_GET,'addr')) { $addr = trim($_GET['addr']); $res = probe_url($addr); @@ -14,4 +14,3 @@ function acctlink_init(&$a) { } } } -} diff --git a/mod/acl.php b/mod/acl.php index 5666ccabb8..f5e04b96a7 100644 --- a/mod/acl.php +++ b/mod/acl.php @@ -3,8 +3,8 @@ require_once("include/acl_selectors.php"); -if(! function_exists('acl_init')) { function acl_init(&$a){ acl_lookup($a); } -} + + diff --git a/mod/admin.php b/mod/admin.php index ff17c0b8c4..7f9000807b 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -2,7 +2,7 @@ /** * @file mod/admin.php - * + * * @brief Friendica admin */ @@ -23,7 +23,6 @@ require_once("include/text.php"); * @param App $a * */ -if(! function_exists('admin_post')) { function admin_post(&$a){ @@ -111,7 +110,6 @@ function admin_post(&$a){ goaway($a->get_baseurl(true) . '/admin' ); return; // NOTREACHED } -} /** * @brief Generates content of the admin panel pages @@ -130,7 +128,6 @@ function admin_post(&$a){ * @param App $a * @return string */ -if(! function_exists('admin_content')) { function admin_content(&$a) { if(!is_site_admin()) { @@ -248,7 +245,6 @@ function admin_content(&$a) { return $o; } } -} /** * @brief Subpage with some stats about "the federation" network @@ -264,7 +260,6 @@ function admin_content(&$a) { * @param App $a * @return string */ -if(! function_exists('admin_page_federation')) { function admin_page_federation(&$a) { // get counts on active friendica, diaspora, redmatrix, hubzilla, gnu // social and statusnet nodes this node is knowing @@ -289,7 +284,7 @@ function admin_page_federation(&$a) { // what versions for that platform do we know at all? // again only the active nodes $v = q('SELECT count(*) AS total, version FROM gserver - WHERE last_contact > last_failure AND platform LIKE "%s" + WHERE last_contact > last_failure AND platform LIKE "%s" GROUP BY version ORDER BY version;', $p); @@ -306,12 +301,12 @@ function admin_page_federation(&$a) { $newVC = $vv['total']; $newVV = $vv['version']; $posDash = strpos($newVV, '-'); - if($posDash) + if($posDash) $newVV = substr($newVV, 0, $posDash); if(isset($newV[$newVV])) - $newV[$newVV] += $newVC; + $newV[$newVV] += $newVC; else - $newV[$newVV] = $newVC; + $newV[$newVV] = $newVC; } foreach ($newV as $key => $value) { array_push($newVv, array('total'=>$value, 'version'=>$key)); @@ -366,7 +361,6 @@ function admin_page_federation(&$a) { '$baseurl' => $a->get_baseurl(), )); } -} /** * @brief Admin Inspect Queue Page @@ -381,7 +375,6 @@ function admin_page_federation(&$a) { * @param App $a * @return string */ -if(! function_exists('admin_page_queue')) { function admin_page_queue(&$a) { // get content from the queue table $r = q("SELECT c.name,c.nurl,q.id,q.network,q.created,q.last from queue as q, contact as c where c.id=q.cid order by q.cid, q.created;"); @@ -401,7 +394,6 @@ function admin_page_queue(&$a) { '$entries' => $r, )); } -} /** * @brief Admin Summary Page @@ -414,7 +406,6 @@ function admin_page_queue(&$a) { * @param App $a * @return string */ -if(! function_exists('admin_page_summary')) { function admin_page_summary(&$a) { $r = q("SELECT `page-flags`, COUNT(uid) as `count` FROM `user` GROUP BY `page-flags`"); $accounts = array( @@ -461,14 +452,12 @@ function admin_page_summary(&$a) { '$plugins' => array( t('Active plugins'), $a->plugins ) )); } -} /** * @brief Process send data from Admin Site Page - * + * * @param App $a */ -if(! function_exists('admin_page_site_post')) { function admin_page_site_post(&$a) { if(!x($_POST,"page_site")) { return; @@ -781,7 +770,6 @@ function admin_page_site_post(&$a) { return; // NOTREACHED } -} /** * @brief Generate Admin Site subpage @@ -791,7 +779,6 @@ function admin_page_site_post(&$a) { * @param App $a * @return string */ -if(! function_exists('admin_page_site')) { function admin_page_site(&$a) { /* Installed langs */ @@ -996,7 +983,7 @@ function admin_page_site(&$a) { '$form_security_token' => get_form_security_token("admin_site") )); -} + } /** @@ -1011,7 +998,6 @@ function admin_page_site(&$a) { * @param App $a * @return string **/ -if(! function_exists('admin_page_dbsync')) { function admin_page_dbsync(&$a) { $o = ''; @@ -1087,15 +1073,14 @@ function admin_page_dbsync(&$a) { } return $o; -} + } /** * @brief Process data send by Users admin page - * + * * @param App $a */ -if(! function_exists('admin_page_users_post')) { function admin_page_users_post(&$a){ $pending = ( x($_POST, 'pending') ? $_POST['pending'] : array() ); $users = ( x($_POST, 'user') ? $_POST['user'] : array() ); @@ -1186,7 +1171,6 @@ function admin_page_users_post(&$a){ goaway($a->get_baseurl(true) . '/admin/users' ); return; // NOTREACHED } -} /** * @brief Admin panel subpage for User management @@ -1200,7 +1184,6 @@ function admin_page_users_post(&$a){ * @param App $a * @return string */ -if(! function_exists('admin_page_users')) { function admin_page_users(&$a){ if($a->argc>2) { $uid = $a->argv[3]; @@ -1353,7 +1336,7 @@ function admin_page_users(&$a){ $o .= paginate($a); return $o; } -} + /** * @brief Plugins admin page @@ -1371,7 +1354,6 @@ function admin_page_users(&$a){ * @param App $a * @return string */ -if(! function_exists('admin_page_plugins')) { function admin_page_plugins(&$a){ /* @@ -1497,19 +1479,17 @@ function admin_page_plugins(&$a){ '$baseurl' => $a->get_baseurl(true), '$function' => 'plugins', '$plugins' => $plugins, - '$pcount' => count($plugins), + '$pcount' => count($plugins), '$noplugshint' => sprintf( t('There are currently no plugins available on your node. You can find the official plugin repository at %1$s and might find other interesting plugins in the open plugin registry at %2$s'), 'https://github.com/friendica/friendica-addons', 'http://addons.friendi.ca'), '$form_security_token' => get_form_security_token("admin_themes"), )); } -} /** * @param array $themes * @param string $th * @param int $result */ -if(! function_exists('toggle_theme')) { function toggle_theme(&$themes,$th,&$result) { for($x = 0; $x < count($themes); $x ++) { if($themes[$x]['name'] === $th) { @@ -1524,14 +1504,12 @@ function toggle_theme(&$themes,$th,&$result) { } } } -} /** * @param array $themes * @param string $th * @return int */ -if(! function_exists('theme_status')) { function theme_status($themes,$th) { for($x = 0; $x < count($themes); $x ++) { if($themes[$x]['name'] === $th) { @@ -1545,13 +1523,12 @@ function theme_status($themes,$th) { } return 0; } -} + /** * @param array $themes * @return string */ -if(! function_exists('rebuild_theme_table')) { function rebuild_theme_table($themes) { $o = ''; if(count($themes)) { @@ -1565,7 +1542,7 @@ function rebuild_theme_table($themes) { } return $o; } -} + /** * @brief Themes admin page @@ -1583,7 +1560,6 @@ function rebuild_theme_table($themes) { * @param App $a * @return string */ -if(! function_exists('admin_page_themes')) { function admin_page_themes(&$a){ $allowed_themes_str = get_config('system','allowed_themes'); @@ -1758,14 +1734,13 @@ function admin_page_themes(&$a){ '$form_security_token' => get_form_security_token("admin_themes"), )); } -} + /** * @brief Prosesses data send by Logs admin page - * + * * @param App $a */ -if(! function_exists('admin_page_logs_post')) { function admin_page_logs_post(&$a) { if(x($_POST,"page_logs")) { check_form_security_token_redirectOnErr('/admin/logs', 'admin_logs'); @@ -1783,7 +1758,6 @@ function admin_page_logs_post(&$a) { goaway($a->get_baseurl(true) . '/admin/logs' ); return; // NOTREACHED } -} /** * @brief Generates admin panel subpage for configuration of the logs @@ -1801,7 +1775,6 @@ function admin_page_logs_post(&$a) { * @param App $a * @return string */ -if(! function_exists('admin_page_logs')) { function admin_page_logs(&$a){ $log_choices = array( @@ -1833,7 +1806,6 @@ function admin_page_logs(&$a){ '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE );\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');", )); } -} /** * @brief Generates admin panel subpage to view the Friendica log @@ -1853,7 +1825,6 @@ function admin_page_logs(&$a){ * @param App $a * @return string */ -if(! function_exists('admin_page_viewlogs')) { function admin_page_viewlogs(&$a){ $t = get_markup_template("admin_viewlogs.tpl"); $f = get_config('system','logfile'); @@ -1890,14 +1861,12 @@ function admin_page_viewlogs(&$a){ '$logname' => get_config('system','logfile') )); } -} /** * @brief Prosesses data send by the features admin page - * + * * @param App $a */ -if(! function_exists('admin_page_features_post')) { function admin_page_features_post(&$a) { check_form_security_token_redirectOnErr('/admin/features', 'admin_manage_features'); @@ -1929,25 +1898,23 @@ function admin_page_features_post(&$a) { goaway($a->get_baseurl(true) . '/admin/features' ); return; // NOTREACHED } -} /** * @brief Subpage for global additional feature management - * + * * This functin generates the subpage 'Manage Additional Features' * for the admin panel. At this page the admin can set preferences - * for the user settings of the 'additional features'. If needed this + * for the user settings of the 'additional features'. If needed this * preferences can be locked through the admin. - * + * * The returned string contains the HTML code of the subpage 'Manage * Additional Features' - * + * * @param App $a * @return string */ -if(! function_exists('admin_page_features')) { function admin_page_features(&$a) { - + if((argc() > 1) && (argv(1) === 'features')) { $arr = array(); $features = get_features(false); @@ -1966,7 +1933,7 @@ function admin_page_features(&$a) { ); } } - + $tpl = get_markup_template("admin_settings_features.tpl"); $o .= replace_macros($tpl, array( '$form_security_token' => get_form_security_token("admin_manage_features"), @@ -1978,4 +1945,3 @@ function admin_page_features(&$a) { return $o; } } -} diff --git a/mod/allfriends.php b/mod/allfriends.php index 8843265a99..356a389b83 100644 --- a/mod/allfriends.php +++ b/mod/allfriends.php @@ -5,7 +5,6 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); require_once('mod/contacts.php'); -if(! function_exists('allfriends_content')) { function allfriends_content(&$a) { $o = ''; @@ -98,4 +97,3 @@ function allfriends_content(&$a) { return $o; } -} diff --git a/mod/amcd.php b/mod/amcd.php index 141a804298..a2a1327e6d 100644 --- a/mod/amcd.php +++ b/mod/amcd.php @@ -1,5 +1,5 @@ <?php -if(! function_exists('amcd_content')) { + function amcd_content(&$a) { //header("Content-type: text/json"); echo <<< EOT @@ -46,5 +46,4 @@ echo <<< EOT } EOT; killme(); -} -} +} \ No newline at end of file diff --git a/mod/api.php b/mod/api.php index 67564836e8..da2c40c305 100644 --- a/mod/api.php +++ b/mod/api.php @@ -1,8 +1,10 @@ <?php + require_once('include/api.php'); -if(! function_exists('oauth_get_client')) { function oauth_get_client($request){ + + $params = $request->get_parameters(); $token = $params['oauth_token']; @@ -17,10 +19,9 @@ function oauth_get_client($request){ return $r[0]; } -} -if(! function_exists('api_post')) { function api_post(&$a) { + if(! local_user()) { notice( t('Permission denied.') . EOL); return; @@ -30,10 +31,9 @@ function api_post(&$a) { notice( t('Permission denied.') . EOL); return; } -} + } -if(! function_exists('api_content')) { function api_content(&$a) { if ($a->cmd=='api/oauth/authorize'){ /* @@ -114,4 +114,3 @@ function api_content(&$a) { echo api_call($a); killme(); } -} diff --git a/mod/apps.php b/mod/apps.php index e807feae74..a821ef5d5b 100644 --- a/mod/apps.php +++ b/mod/apps.php @@ -1,23 +1,25 @@ <?php -if(! function_exists('apps_content')) { + function apps_content(&$a) { - $privateaddons = get_config('config','private_addons'); - if ($privateaddons === "1") { - if((! (local_user()))) { - info( t("You must be logged in to use addons. ")); - return; - } - } - - $title = t('Applications'); - - if(count($a->apps)==0) - notice( t('No installed applications.') . EOL); - - $tpl = get_markup_template("apps.tpl"); - return replace_macros($tpl, array( - '$title' => $title, - '$apps' => $a->apps, - )); + $privateaddons = get_config('config','private_addons'); + if ($privateaddons === "1") { + if((! (local_user()))) { + info( t("You must be logged in to use addons. ")); + return;}; } + + $title = t('Applications'); + + if(count($a->apps)==0) + notice( t('No installed applications.') . EOL); + + + $tpl = get_markup_template("apps.tpl"); + return replace_macros($tpl, array( + '$title' => $title, + '$apps' => $a->apps, + )); + + + } diff --git a/mod/attach.php b/mod/attach.php index 849faa26ec..03f850f0d1 100644 --- a/mod/attach.php +++ b/mod/attach.php @@ -1,7 +1,7 @@ <?php + require_once('include/security.php'); -if(! function_exists('attach_init')) { function attach_init(&$a) { if($a->argc != 2) { @@ -47,4 +47,3 @@ function attach_init(&$a) { killme(); // NOTREACHED } -} diff --git a/mod/babel.php b/mod/babel.php index 56455bdb21..d31e090c55 100644 --- a/mod/babel.php +++ b/mod/babel.php @@ -9,56 +9,55 @@ function visible_lf($s) { return str_replace("\n",'<br />', $s); } -if(! function_exists('babel_content')) { function babel_content(&$a) { $o .= '<h1>Babel Diagnostic</h1>'; $o .= '<form action="babel" method="post">'; $o .= t('Source (bbcode) text:') . EOL . '<textarea name="text" >' . htmlspecialchars($_REQUEST['text']) .'</textarea>' . EOL; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; $o .= '<form action="babel" method="post">'; $o .= t('Source (Diaspora) text to convert to BBcode:') . EOL . '<textarea name="d2bbtext" >' . htmlspecialchars($_REQUEST['d2bbtext']) .'</textarea>' . EOL; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; if(x($_REQUEST,'text')) { $text = trim($_REQUEST['text']); - $o .= "<h2>" . t("Source input: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($text) . EOL. EOL; + $o .= "<h2>" . t("Source input: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($text) . EOL. EOL; $html = bbcode($text); - $o .= "<h2>" . t("bb2html (raw HTML): ") . "</h2>" . EOL. EOL; - $o .= htmlspecialchars($html). EOL. EOL; + $o .= "<h2>" . t("bb2html (raw HTML): ") . "</h2>" . EOL. EOL; + $o .= htmlspecialchars($html). EOL. EOL; //$html = bbcode($text); - $o .= "<h2>" . t("bb2html: ") . "</h2>" . EOL. EOL; - $o .= $html. EOL. EOL; + $o .= "<h2>" . t("bb2html: ") . "</h2>" . EOL. EOL; + $o .= $html. EOL. EOL; $bbcode = html2bbcode($html); - $o .= "<h2>" . t("bb2html2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bbcode) . EOL. EOL; + $o .= "<h2>" . t("bb2html2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bbcode) . EOL. EOL; $diaspora = bb2diaspora($text); - $o .= "<h2>" . t("bb2md: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($diaspora) . EOL. EOL; + $o .= "<h2>" . t("bb2md: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($diaspora) . EOL. EOL; $html = Markdown($diaspora); - $o .= "<h2>" . t("bb2md2html: ") . "</h2>" . EOL. EOL; - $o .= $html. EOL. EOL; + $o .= "<h2>" . t("bb2md2html: ") . "</h2>" . EOL. EOL; + $o .= $html. EOL. EOL; $bbcode = diaspora2bb($diaspora); - $o .= "<h2>" . t("bb2dia2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bbcode) . EOL. EOL; + $o .= "<h2>" . t("bb2dia2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bbcode) . EOL. EOL; $bbcode = html2bbcode($html); - $o .= "<h2>" . t("bb2md2html2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bbcode) . EOL. EOL; + $o .= "<h2>" . t("bb2md2html2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bbcode) . EOL. EOL; @@ -67,15 +66,14 @@ function babel_content(&$a) { if(x($_REQUEST,'d2bbtext')) { $d2bbtext = trim($_REQUEST['d2bbtext']); - $o .= "<h2>" . t("Source input (Diaspora format): ") . "</h2>" . EOL. EOL; - $o .= visible_lf($d2bbtext) . EOL. EOL; + $o .= "<h2>" . t("Source input (Diaspora format): ") . "</h2>" . EOL. EOL; + $o .= visible_lf($d2bbtext) . EOL. EOL; $bb = diaspora2bb($d2bbtext); - $o .= "<h2>" . t("diaspora2bb: ") . "</h2>" . EOL. EOL; - $o .= visible_lf($bb) . EOL. EOL; + $o .= "<h2>" . t("diaspora2bb: ") . "</h2>" . EOL. EOL; + $o .= visible_lf($bb) . EOL. EOL; } return $o; } -} diff --git a/mod/bookmarklet.php b/mod/bookmarklet.php index 4db6bf401e..be8645c1fd 100644 --- a/mod/bookmarklet.php +++ b/mod/bookmarklet.php @@ -1,14 +1,12 @@ <?php + require_once('include/conversation.php'); require_once('include/items.php'); -if(! function_exists('bookmarklet_init')) { function bookmarklet_init(&$a) { $_GET["mode"] = "minimal"; } -} -if(! function_exists('bookmarklet_content')) { function bookmarklet_content(&$a) { if(!local_user()) { $o = '<h2>'.t('Login').'</h2>'; @@ -46,4 +44,3 @@ function bookmarklet_content(&$a) { return $o; } -} diff --git a/mod/cb.php b/mod/cb.php index 04d01302c1..6375d23984 100644 --- a/mod/cb.php +++ b/mod/cb.php @@ -4,28 +4,21 @@ * General purpose landing page for plugins/addons */ -if(! function_exists('cb_init')) { + function cb_init(&$a) { call_hooks('cb_init'); } -} -if(! function_exists('cb_post')) { function cb_post(&$a) { call_hooks('cb_post', $_POST); } -} -if(! function_exists('cb_afterpost')) { function cb_afterpost(&$a) { call_hooks('cb_afterpost'); } -} -if(! function_exists('cb_content')) { function cb_content(&$a) { $o = ''; call_hooks('cb_content', $o); return $o; -} -} +} \ No newline at end of file diff --git a/mod/common.php b/mod/common.php index 4cdbe9641b..c9409b3ef1 100644 --- a/mod/common.php +++ b/mod/common.php @@ -5,7 +5,6 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); require_once('mod/contacts.php'); -if(! function_exists('common_content')) { function common_content(&$a) { $o = ''; @@ -145,4 +144,3 @@ function common_content(&$a) { return $o; } -} diff --git a/mod/community.php b/mod/community.php index c64c6216b1..b6d72a3555 100644 --- a/mod/community.php +++ b/mod/community.php @@ -1,14 +1,15 @@ <?php -if(! function_exists('community_init')) { + function community_init(&$a) { if(! local_user()) { unset($_SESSION['theme']); unset($_SESSION['mobile-theme']); } -} + + } -if(! function_exists('community_content')) { + function community_content(&$a, $update = 0) { $o = ''; @@ -114,9 +115,7 @@ function community_content(&$a, $update = 0) { return $o; } -} -if(! function_exists('community_getitems')) { function community_getitems($start, $itemspage) { if (get_config('system','community_page_style') == CP_GLOBAL_COMMUNITY) return(community_getpublicitems($start, $itemspage)); @@ -141,10 +140,9 @@ function community_getitems($start, $itemspage) { ); return($r); -} + } -if(! function_exists('community_getpublicitems')) { function community_getpublicitems($start, $itemspage) { $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`, `author-name` AS `name`, `owner-avatar` AS `photo`, @@ -159,4 +157,3 @@ function community_getpublicitems($start, $itemspage) { return($r); } -} diff --git a/mod/contactgroup.php b/mod/contactgroup.php index 0291350b21..bf81afe079 100644 --- a/mod/contactgroup.php +++ b/mod/contactgroup.php @@ -2,7 +2,6 @@ require_once('include/group.php'); -if(! function_exists('contactgroup_content')) { function contactgroup_content(&$a) { @@ -48,5 +47,4 @@ function contactgroup_content(&$a) { } killme(); -} -} +} \ No newline at end of file diff --git a/mod/contacts.php b/mod/contacts.php index cef8bdb897..0b421433e0 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -7,7 +7,6 @@ require_once('include/Scrape.php'); require_once('mod/proxy.php'); require_once('include/Photo.php'); -if(! function_exists('contacts_init')) { function contacts_init(&$a) { if(! local_user()) return; @@ -39,7 +38,7 @@ function contacts_init(&$a) { if (($a->data['contact']['network'] != "") AND ($a->data['contact']['network'] != NETWORK_DFRN)) { $networkname = format_network_name($a->data['contact']['network'],$a->data['contact']['url']); - } else + } else $networkname = ''; $vcard_widget = replace_macros(get_markup_template("vcard-widget.tpl"),array( @@ -89,10 +88,9 @@ function contacts_init(&$a) { '$base' => $base )); -} + } -if(! function_exists('contacts_batch_actions')) { function contacts_batch_actions(&$a){ $contacts_id = $_POST['contact_batch']; if (!is_array($contacts_id)) return; @@ -134,10 +132,10 @@ function contacts_batch_actions(&$a){ goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); else goaway($a->get_baseurl(true) . '/contacts'); -} + } -if(! function_exists('contacts_post')) { + function contacts_post(&$a) { if(! local_user()) @@ -217,11 +215,10 @@ function contacts_post(&$a) { $a->data['contact'] = $r[0]; return; -} + } /*contact actions*/ -if(! function_exists('_contact_update')) { function _contact_update($contact_id) { $r = q("SELECT `uid`, `url`, `network` FROM `contact` WHERE `id` = %d", intval($contact_id)); if (!$r) @@ -242,9 +239,7 @@ function _contact_update($contact_id) { // pull feed and consume it, which should subscribe to the hub. proc_run('php',"include/onepoll.php","$contact_id", "force"); } -} -if(! function_exists('_contact_update_profile')) { function _contact_update_profile($contact_id) { $r = q("SELECT `uid`, `url`, `network` FROM `contact` WHERE `id` = %d", intval($contact_id)); if (!$r) @@ -304,9 +299,7 @@ function _contact_update_profile($contact_id) { // Update the entry in the gcontact table update_gcontact_from_probe($data["url"]); } -} -if(! function_exists('_contact_block')) { function _contact_block($contact_id, $orig_record) { $blocked = (($orig_record['blocked']) ? 0 : 1); $r = q("UPDATE `contact` SET `blocked` = %d WHERE `id` = %d AND `uid` = %d", @@ -315,10 +308,8 @@ function _contact_block($contact_id, $orig_record) { intval(local_user()) ); return $r; -} -} -if(! function_exists('_contact_ignore')) { +} function _contact_ignore($contact_id, $orig_record) { $readonly = (($orig_record['readonly']) ? 0 : 1); $r = q("UPDATE `contact` SET `readonly` = %d WHERE `id` = %d AND `uid` = %d", @@ -328,9 +319,6 @@ function _contact_ignore($contact_id, $orig_record) { ); return $r; } -} - -if(! function_exists('_contact_archive')) { function _contact_archive($contact_id, $orig_record) { $archived = (($orig_record['archive']) ? 0 : 1); $r = q("UPDATE `contact` SET `archive` = %d WHERE `id` = %d AND `uid` = %d", @@ -343,18 +331,14 @@ function _contact_archive($contact_id, $orig_record) { } return $r; } -} - -if(! function_exists('_contact_drop')) { function _contact_drop($contact_id, $orig_record) { $a = get_app(); terminate_friendship($a->user,$a->contact,$orig_record); contact_remove($orig_record['id']); } -} -if(! function_exists('contacts_content')) { + function contacts_content(&$a) { $sort_type = 0; @@ -815,9 +799,7 @@ function contacts_content(&$a) { return $o; } -} -if(! function_exists('contacts_tab')) { function contacts_tab($a, $contact_id, $active_tab) { // tabs $tabs = array( @@ -891,9 +873,7 @@ function contacts_tab($a, $contact_id, $active_tab) { return $tab_str; } -} -if(! function_exists('contact_posts')) { function contact_posts($a, $contact_id) { $r = q("SELECT `url` FROM `contact` WHERE `id` = %d", intval($contact_id)); @@ -921,9 +901,7 @@ function contact_posts($a, $contact_id) { return $o; } -} -if(! function_exists('_contact_detail_for_template')) { function _contact_detail_for_template($rr){ $community = ''; @@ -974,5 +952,5 @@ function _contact_detail_for_template($rr){ 'url' => $url, 'network' => network_to_name($rr['network'], $rr['url']), ); -} + } diff --git a/mod/content.php b/mod/content.php index ab0fe7e4bf..c5a5556116 100644 --- a/mod/content.php +++ b/mod/content.php @@ -15,7 +15,7 @@ // fast - e.g. one or two milliseconds to fetch parent items for the current content, // and 10-20 milliseconds to fetch all the child items. -if(! function_exists('content_content')) { + function content_content(&$a, $update = 0) { require_once('include/conversation.php'); @@ -61,7 +61,7 @@ function content_content(&$a, $update = 0) { $o = ''; - + $contact_id = $a->cid; @@ -100,7 +100,7 @@ function content_content(&$a, $update = 0) { $def_acl = array('allow_cid' => $str); } - + $sql_options = (($star) ? " and starred = 1 " : ''); $sql_options .= (($bmark) ? " and bookmark = 1 " : ''); @@ -137,7 +137,7 @@ function content_content(&$a, $update = 0) { } elseif($cid) { - $r = q("SELECT `id`,`name`,`network`,`writable`,`nurl` FROM `contact` WHERE `id` = %d + $r = q("SELECT `id`,`name`,`network`,`writable`,`nurl` FROM `contact` WHERE `id` = %d AND `blocked` = 0 AND `pending` = 0 LIMIT 1", intval($cid) ); @@ -304,9 +304,9 @@ function content_content(&$a, $update = 0) { echo json_encode($o); killme(); } -} -if(! function_exists('render_content')) { + + function render_content(&$a, $items, $mode, $update, $preview = false) { require_once('include/bbcode.php'); @@ -373,7 +373,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if($mode === 'network-new' || $mode === 'search' || $mode === 'community') { - // "New Item View" on network page or search page results + // "New Item View" on network page or search page results // - just loop through the items and format them minimally for display //$tpl = get_markup_template('search_item.tpl'); @@ -389,7 +389,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $sparkle = ''; if($mode === 'search' || $mode === 'community') { - if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) + if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) continue; $nickname = $item['nickname']; @@ -436,7 +436,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $drop = array( 'dropping' => $dropping, - 'select' => t('Select'), + 'select' => t('Select'), 'delete' => t('Delete'), ); @@ -526,11 +526,11 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $comments[$item['parent']] = 1; else $comments[$item['parent']] += 1; - } elseif(! x($comments,$item['parent'])) + } elseif(! x($comments,$item['parent'])) $comments[$item['parent']] = 0; // avoid notices later on } - // map all the like/dislike activities for each parent item + // map all the like/dislike activities for each parent item // Store these in the $alike and $dlike arrays foreach($items as $item) { @@ -617,14 +617,14 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $redirect_url = $a->get_baseurl($ssl_state) . '/redir/' . $item['cid'] ; - $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) + $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) ? t('Private Message') : false); // Top-level wall post not written by the wall owner (wall-to-wall) - // First figure out who owns it. + // First figure out who owns it. $osparkle = ''; @@ -651,13 +651,13 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if((! $owner_linkmatch) && (! $alias_linkmatch) && (! $owner_namematch)) { // The author url doesn't match the owner (typically the contact) - // and also doesn't match the contact alias. - // The name match is a hack to catch several weird cases where URLs are + // and also doesn't match the contact alias. + // The name match is a hack to catch several weird cases where URLs are // all over the park. It can be tricked, but this prevents you from // seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn - // well that it's the same Bob Smith. + // well that it's the same Bob Smith. - // But it could be somebody else with the same name. It just isn't highly likely. + // But it could be somebody else with the same name. It just isn't highly likely. $owner_url = $item['owner-link']; @@ -666,7 +666,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $template = $wallwall; $commentww = 'ww'; // If it is our contact, use a friendly redirect link - if((link_compare($item['owner-link'],$item['url'])) + if((link_compare($item['owner-link'],$item['url'])) && ($item['network'] === NETWORK_DFRN)) { $owner_url = $redirect_url; $osparkle = ' sparkle'; @@ -678,7 +678,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { } $likebuttons = ''; - $shareable = ((($profile_owner == local_user()) && ($item['private'] != 1)) ? true : false); + $shareable = ((($profile_owner == local_user()) && ($item['private'] != 1)) ? true : false); if($page_writeable) { /* if($toplevelpost) { */ @@ -698,7 +698,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if(($show_comment_box) || (($show_comment_box == false) && ($override_comment_box == false) && ($item['last-child']))) { $comment = replace_macros($cmnt_tpl,array( - '$return_path' => '', + '$return_path' => '', '$jsreload' => (($mode === 'display') ? $_SESSION['return_url'] : ''), '$type' => (($mode === 'profile') ? 'wall-comment' : 'net-comment'), '$id' => $item['item_id'], @@ -739,7 +739,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $drop = array( 'dropping' => $dropping, - 'select' => t('Select'), + 'select' => t('Select'), 'delete' => t('Delete'), ); @@ -805,9 +805,9 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $shiny = ""; if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0) - $shiny = 'shiny'; + $shiny = 'shiny'; - // + // localize_item($item); @@ -897,5 +897,5 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { return $threads; -} + } diff --git a/mod/credits.php b/mod/credits.php index 8e6321760b..f8cfb03f37 100644 --- a/mod/credits.php +++ b/mod/credits.php @@ -5,7 +5,6 @@ * addons repository will be listed though ATM) */ -if(! function_exists('credits_content')) { function credits_content (&$a) { /* fill the page with credits */ $f = fopen('util/credits.txt','r'); @@ -19,4 +18,3 @@ function credits_content (&$a) { '$names' => $arr, )); } -} diff --git a/mod/crepair.php b/mod/crepair.php index 50502b4987..5b4db09dac 100644 --- a/mod/crepair.php +++ b/mod/crepair.php @@ -2,7 +2,6 @@ require_once("include/contact_selectors.php"); require_once("mod/contacts.php"); -if(! function_exists('crepair_init')) { function crepair_init(&$a) { if(! local_user()) return; @@ -29,9 +28,8 @@ function crepair_init(&$a) { profile_load($a, "", 0, get_contact_details_by_url($contact["url"])); } } -} -if(! function_exists('crepair_post')) { + function crepair_post(&$a) { if(! local_user()) return; @@ -93,9 +91,9 @@ function crepair_post(&$a) { return; } -} -if(! function_exists('crepair_content')) { + + function crepair_content(&$a) { if(! local_user()) { @@ -182,5 +180,5 @@ function crepair_content(&$a) { )); return $o; -} + } diff --git a/mod/delegate.php b/mod/delegate.php index d421de3764..20d2e605e0 100644 --- a/mod/delegate.php +++ b/mod/delegate.php @@ -1,13 +1,11 @@ <?php require_once('mod/settings.php'); -if(! function_exists('delegate_init')) { function delegate_init(&$a) { return settings_init($a); } -} -if(! function_exists('delegate_content')) { + function delegate_content(&$a) { if(! local_user()) { @@ -92,12 +90,12 @@ function delegate_content(&$a) { // find every contact who might be a candidate for delegation - $r = q("select nurl from contact where substring_index(contact.nurl,'/',3) = '%s' + $r = q("select nurl from contact where substring_index(contact.nurl,'/',3) = '%s' and contact.uid = %d and contact.self = 0 and network = '%s' ", dbesc(normalise_link($a->get_baseurl())), intval(local_user()), dbesc(NETWORK_DFRN) - ); + ); if(! count($r)) { notice( t('No potential page delegates located.') . EOL); @@ -146,5 +144,5 @@ function delegate_content(&$a) { return $o; -} + } diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php index 00e215e334..27c04a908d 100644 --- a/mod/dfrn_confirm.php +++ b/mod/dfrn_confirm.php @@ -16,7 +16,6 @@ require_once('include/enotify.php'); -if(! function_exists('dfrn_confirm_post')) { function dfrn_confirm_post(&$a,$handsfree = null) { if(is_array($handsfree)) { @@ -802,5 +801,5 @@ function dfrn_confirm_post(&$a,$handsfree = null) { goaway(z_root()); // NOTREACHED -} + } diff --git a/mod/dfrn_notify.php b/mod/dfrn_notify.php index 04500e89ad..4aa777b550 100644 --- a/mod/dfrn_notify.php +++ b/mod/dfrn_notify.php @@ -5,7 +5,6 @@ require_once('include/event.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); -if(! function_exists('dfrn_notify_post')) { function dfrn_notify_post(&$a) { logger(__function__, LOGGER_TRACE); $dfrn_id = ((x($_POST,'dfrn_id')) ? notags(trim($_POST['dfrn_id'])) : ''); @@ -214,9 +213,8 @@ function dfrn_notify_post(&$a) { // NOTREACHED } -} -if(! function_exists('dfrn_notify_content')) { + function dfrn_notify_content(&$a) { if(x($_GET,'dfrn_id')) { @@ -340,5 +338,5 @@ function dfrn_notify_content(&$a) { killme(); } -} + } diff --git a/mod/dfrn_poll.php b/mod/dfrn_poll.php index 82c75d28cf..ab6637607e 100644 --- a/mod/dfrn_poll.php +++ b/mod/dfrn_poll.php @@ -3,7 +3,7 @@ require_once('include/items.php'); require_once('include/auth.php'); require_once('include/dfrn.php'); -if(! function_exists('dfrn_poll_init')) { + function dfrn_poll_init(&$a) { @@ -160,7 +160,7 @@ function dfrn_poll_init(&$a) { if($final_dfrn_id != $orig_id) { logger('profile_check: ' . $final_dfrn_id . ' != ' . $orig_id, LOGGER_DEBUG); - // did not decode properly - cannot trust this site + // did not decode properly - cannot trust this site xml_status(3, 'Bad decryption'); } @@ -195,11 +195,11 @@ function dfrn_poll_init(&$a) { return; // NOTREACHED } } -} + } -if(! function_exists('dfrn_poll_post')) { + function dfrn_poll_post(&$a) { $dfrn_id = ((x($_POST,'dfrn_id')) ? $_POST['dfrn_id'] : ''); @@ -257,7 +257,7 @@ function dfrn_poll_post(&$a) { if($final_dfrn_id != $orig_id) { logger('profile_check: ' . $final_dfrn_id . ' != ' . $orig_id, LOGGER_DEBUG); - // did not decode properly - cannot trust this site + // did not decode properly - cannot trust this site xml_status(3, 'Bad decryption'); } @@ -377,9 +377,7 @@ function dfrn_poll_post(&$a) { } } -} -if(! function_exists('dfrn_poll_content')) { function dfrn_poll_content(&$a) { $dfrn_id = ((x($_GET,'dfrn_id')) ? $_GET['dfrn_id'] : ''); @@ -564,4 +562,3 @@ function dfrn_poll_content(&$a) { } } } -} diff --git a/mod/directory.php b/mod/directory.php index 7ce1530efc..294a55585d 100644 --- a/mod/directory.php +++ b/mod/directory.php @@ -1,5 +1,5 @@ <?php -if(! function_exists('directory_init')) { + function directory_init(&$a) { $a->set_pager_itemspage(60); @@ -16,23 +16,23 @@ function directory_init(&$a) { unset($_SESSION['mobile-theme']); } -} + } -if(! function_exists('directory_post')) { + function directory_post(&$a) { if(x($_POST,'search')) $a->data['search'] = $_POST['search']; } -} -if(! function_exists('directory_content')) { + + function directory_content(&$a) { global $db; require_once("mod/proxy.php"); - if((get_config('system','block_public')) && (! local_user()) && (! remote_user()) || + if((get_config('system','block_public')) && (! local_user()) && (! remote_user()) || (get_config('system','block_local_dir')) && (! local_user()) && (! remote_user())) { notice( t('Public access denied.') . EOL); return; @@ -123,14 +123,14 @@ function directory_content(&$a) { } // if(strlen($rr['dob'])) { // if(($years = age($rr['dob'],$rr['timezone'],'')) != 0) -// $details .= '<br />' . t('Age: ') . $years ; +// $details .= '<br />' . t('Age: ') . $years ; // } // if(strlen($rr['gender'])) // $details .= '<br />' . t('Gender: ') . $rr['gender']; // show if account is a community account - /// @TODO The other page types should be also respected, but first we need a good + /// @TODO The other page types should be also respected, but first we need a good /// translatiion and systemwide consistency for displaying the page type if((intval($rr['page-flags']) == PAGE_COMMUNITY) OR (intval($rr['page-flags']) == PAGE_PRVGROUP)) $community = true; @@ -158,7 +158,7 @@ function directory_content(&$a) { else { $location_e = $location; } - + $photo_menu = array(array(t("View Profile"), zrl($profile_link))); $entry = array( @@ -217,4 +217,3 @@ function directory_content(&$a) { return $o; } -} diff --git a/mod/dirfind.php b/mod/dirfind.php index f5e90705b7..0dfe4d67a9 100644 --- a/mod/dirfind.php +++ b/mod/dirfind.php @@ -5,7 +5,6 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); require_once('mod/contacts.php'); -if(! function_exists('dirfind_init')) { function dirfind_init(&$a) { if(! local_user()) { @@ -20,9 +19,9 @@ function dirfind_init(&$a) { $a->page['aside'] .= follow_widget(); } -} -if(! function_exists('dirfind_content')) { + + function dirfind_content(&$a, $prefix = "") { $community = false; @@ -236,4 +235,3 @@ function dirfind_content(&$a, $prefix = "") { return $o; } -} diff --git a/mod/display.php b/mod/display.php index 9995a2b3ef..4e33927072 100644 --- a/mod/display.php +++ b/mod/display.php @@ -1,5 +1,5 @@ <?php -if(! function_exists('display_init')) { + function display_init(&$a) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -85,10 +85,9 @@ function display_init(&$a) { } profile_load($a, $nick, 0, $profiledata); -} + } -if(! function_exists('display_fetchauthor')) { function display_fetchauthor($a, $item) { $profiledata = array(); @@ -221,9 +220,7 @@ function display_fetchauthor($a, $item) { return($profiledata); } -} -if(! function_exists('display_content')) { function display_content(&$a, $update = 0) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -525,4 +522,4 @@ function display_content(&$a, $update = 0) { return $o; } -} + diff --git a/mod/editpost.php b/mod/editpost.php index ee4d61e60a..9a80d0b2f4 100644 --- a/mod/editpost.php +++ b/mod/editpost.php @@ -2,7 +2,6 @@ require_once('include/acl_selectors.php'); -if(! function_exists('editpost_content')) { function editpost_content(&$a) { $o = ''; @@ -151,5 +150,7 @@ function editpost_content(&$a) { )); return $o; + } -} + + diff --git a/mod/events.php b/mod/events.php index 3dc20e535a..653ae489b8 100644 --- a/mod/events.php +++ b/mod/events.php @@ -5,7 +5,6 @@ require_once('include/datetime.php'); require_once('include/event.php'); require_once('include/items.php'); -if(! function_exists('events_post')) { function events_post(&$a) { logger('post: ' . print_r($_REQUEST,true)); @@ -157,9 +156,9 @@ function events_post(&$a) { goaway($_SESSION['return_url']); } -} -if(! function_exists('events_content')) { + + function events_content(&$a) { if(! local_user()) { @@ -579,4 +578,3 @@ function events_content(&$a) { return $o; } } -} diff --git a/mod/fbrowser.php b/mod/fbrowser.php index 73510ef58a..0a2a7dead5 100644 --- a/mod/fbrowser.php +++ b/mod/fbrowser.php @@ -10,7 +10,6 @@ require_once('include/Photo.php'); /** * @param App $a */ -if(! function_exists('fbrowser_content')) { function fbrowser_content($a){ if (!local_user()) @@ -142,5 +141,5 @@ function fbrowser_content($a){ killme(); } -} + } diff --git a/mod/filer.php b/mod/filer.php index 02b8d68978..4e79f337dc 100644 --- a/mod/filer.php +++ b/mod/filer.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); -if(! function_exists('filer_content')) { + function filer_content(&$a) { if(! local_user()) { @@ -30,9 +30,8 @@ function filer_content(&$a) { '$field' => array('term', t("Save to Folder:"), '', '', $filetags, t('- select -')), '$submit' => t('Save'), )); - + echo $o; } killme(); } -} diff --git a/mod/filerm.php b/mod/filerm.php index be3456b58d..c266082c8f 100644 --- a/mod/filerm.php +++ b/mod/filerm.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('filerm_content')) { function filerm_content(&$a) { if(! local_user()) { @@ -26,4 +25,3 @@ function filerm_content(&$a) { killme(); } -} diff --git a/mod/follow.php b/mod/follow.php index a8fdc31b5a..b92a0d980f 100644 --- a/mod/follow.php +++ b/mod/follow.php @@ -5,7 +5,6 @@ require_once('include/follow.php'); require_once('include/Contact.php'); require_once('include/contact_selectors.php'); -if(! function_exists('follow_content')) { function follow_content(&$a) { if(! local_user()) { @@ -149,9 +148,7 @@ function follow_content(&$a) { return $o; } -} -if(! function_exists('follow_post')) { function follow_post(&$a) { if(! local_user()) { @@ -188,4 +185,3 @@ function follow_post(&$a) { goaway($return_url); // NOTREACHED } -} diff --git a/mod/friendica.php b/mod/friendica.php index 18d045f2d5..aad5964baf 100644 --- a/mod/friendica.php +++ b/mod/friendica.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('friendica_init')) { function friendica_init(&$a) { if ($a->argv[1]=="json"){ $register_policy = Array('REGISTER_CLOSED', 'REGISTER_APPROVE', 'REGISTER_OPEN'); @@ -57,9 +56,9 @@ function friendica_init(&$a) { killme(); } } -} -if(! function_exists('friendica_content')) { + + function friendica_content(&$a) { $o = ''; @@ -71,7 +70,7 @@ function friendica_content(&$a) { $o .= t('This is Friendica, version') . ' ' . FRIENDICA_VERSION . ' '; $o .= t('running at web location') . ' ' . z_root() . '</p><p>'; - $o .= t('Please visit <a href="http://friendica.com">Friendica.com</a> to learn more about the Friendica project.') . '</p><p>'; + $o .= t('Please visit <a href="http://friendica.com">Friendica.com</a> to learn more about the Friendica project.') . '</p><p>'; $o .= t('Bug reports and issues: please visit') . ' ' . '<a href="https://github.com/friendica/friendica/issues?state=open">'.t('the bugtracker at github').'</a></p><p>'; $o .= t('Suggestions, praise, donations, etc. - please email "Info" at Friendica - dot com') . '</p>'; @@ -103,8 +102,8 @@ function friendica_content(&$a) { else $o .= '<p>' . t('No installed plugins/addons/apps') . '</p>'; - call_hooks('about_hook', $o); + call_hooks('about_hook', $o); return $o; -} + } diff --git a/mod/fsuggest.php b/mod/fsuggest.php index 26a5e98063..6b1cbd7533 100644 --- a/mod/fsuggest.php +++ b/mod/fsuggest.php @@ -1,6 +1,6 @@ <?php -if(! function_exists('fsuggest_post')) { + function fsuggest_post(&$a) { if(! local_user()) { @@ -39,11 +39,11 @@ function fsuggest_post(&$a) { VALUES ( %d, %d, '%s','%s','%s','%s','%s','%s')", intval(local_user()), intval($contact_id), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - dbesc($r[0]['request']), - dbesc($r[0]['photo']), - dbesc($hash), + dbesc($r[0]['name']), + dbesc($r[0]['url']), + dbesc($r[0]['request']), + dbesc($r[0]['photo']), + dbesc($hash), dbesc(datetime_convert()) ); $r = q("SELECT `id` FROM `fsuggest` WHERE `note` = '%s' AND `uid` = %d LIMIT 1", @@ -65,11 +65,11 @@ function fsuggest_post(&$a) { } -} + } -if(! function_exists('fsuggest_content')) { + function fsuggest_content(&$a) { require_once('include/acl_selectors.php'); @@ -100,7 +100,7 @@ function fsuggest_content(&$a) { $o .= '<form id="fsuggest-form" action="fsuggest/' . $contact_id . '" method="post" >'; - $o .= contact_selector('suggest','suggest-select', false, + $o .= contact_selector('suggest','suggest-select', false, array('size' => 4, 'exclude' => $contact_id, 'networks' => 'DFRN_ONLY', 'single' => true)); @@ -109,4 +109,3 @@ function fsuggest_content(&$a) { return $o; } -} diff --git a/mod/group.php b/mod/group.php index 2f8053eefb..5b28784f56 100644 --- a/mod/group.php +++ b/mod/group.php @@ -1,21 +1,18 @@ <?php -if(! function_exists('validate_members')) { function validate_members(&$item) { $item = intval($item); } -} -if(! function_exists('group_init')) { function group_init(&$a) { if(local_user()) { require_once('include/group.php'); $a->page['aside'] = group_side('contacts','group','extended',(($a->argc > 1) ? intval($a->argv[1]) : 0)); } } -} -if(! function_exists('group_post')) { + + function group_post(&$a) { if(! local_user()) { @@ -67,9 +64,7 @@ function group_post(&$a) { } return; } -} -if(! function_exists('group_content')) { function group_content(&$a) { $change = false; @@ -234,5 +229,5 @@ function group_content(&$a) { } return replace_macros($tpl, $context); -} + } diff --git a/mod/hcard.php b/mod/hcard.php index af49423de3..6d2d9e2ebf 100644 --- a/mod/hcard.php +++ b/mod/hcard.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('hcard_init')) { function hcard_init(&$a) { $blocked = (((get_config('system','block_public')) && (! local_user()) && (! remote_user())) ? true : false); @@ -16,7 +15,7 @@ function hcard_init(&$a) { $profile = 0; if((local_user()) && ($a->argc > 2) && ($a->argv[2] === 'view')) { $which = $a->user['nickname']; - $profile = $a->argv[1]; + $profile = $a->argv[1]; } profile_load($a,$which,$profile); @@ -24,7 +23,7 @@ function hcard_init(&$a) { if((x($a->profile,'page-flags')) && ($a->profile['page-flags'] == PAGE_COMMUNITY)) { $a->page['htmlhead'] .= '<meta name="friendica.community" content="true" />'; } - if(x($a->profile,'openidserver')) + if(x($a->profile,'openidserver')) $a->page['htmlhead'] .= '<link rel="openid.server" href="' . $a->profile['openidserver'] . '" />' . "\r\n"; if(x($a->profile,'openid')) { $delegate = ((strstr($a->profile['openid'],'://')) ? $a->profile['openid'] : 'http://' . $a->profile['openid']); @@ -43,9 +42,10 @@ function hcard_init(&$a) { $uri = urlencode('acct:' . $a->profile['nickname'] . '@' . $a->get_hostname() . (($a->path) ? '/' . $a->path : '')); $a->page['htmlhead'] .= '<link rel="lrdd" type="application/xrd+xml" href="' . $a->get_baseurl() . '/xrd/?uri=' . $uri . '" />' . "\r\n"; header('Link: <' . $a->get_baseurl() . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false); - + $dfrn_pages = array('request', 'confirm', 'notify', 'poll'); foreach($dfrn_pages as $dfrn) $a->page['htmlhead'] .= "<link rel=\"dfrn-{$dfrn}\" href=\"".$a->get_baseurl()."/dfrn_{$dfrn}/{$which}\" />\r\n"; + } -} + diff --git a/mod/help.php b/mod/help.php index 320e622fa5..5465d3e900 100644 --- a/mod/help.php +++ b/mod/help.php @@ -18,7 +18,6 @@ if (!function_exists('load_doc_file')) { } -if(! function_exists('help_content')) { function help_content(&$a) { nav_set_selected('help'); @@ -99,5 +98,5 @@ function help_content(&$a) { } </style>".$html; return $html; -} + } diff --git a/mod/hostxrd.php b/mod/hostxrd.php index 5b178e9b8f..4121764f1a 100644 --- a/mod/hostxrd.php +++ b/mod/hostxrd.php @@ -2,7 +2,6 @@ require_once('include/crypto.php'); -if(! function_exists('hostxrd_init')) { function hostxrd_init(&$a) { header('Access-Control-Allow-Origin: *'); header("Content-type: text/xml"); @@ -28,5 +27,5 @@ function hostxrd_init(&$a) { )); session_write_close(); exit(); -} + } diff --git a/mod/ignored.php b/mod/ignored.php index 8a681a1154..e876b4ef8b 100644 --- a/mod/ignored.php +++ b/mod/ignored.php @@ -1,6 +1,6 @@ <?php -if(! function_exists('ignored_init')) { + function ignored_init(&$a) { $ignored = 0; @@ -43,4 +43,3 @@ function ignored_init(&$a) { echo json_encode($ignored); killme(); } -} diff --git a/mod/install.php b/mod/install.php index be90acba10..8434b38e38 100755 --- a/mod/install.php +++ b/mod/install.php @@ -3,7 +3,7 @@ require_once "include/Photo.php"; $install_wizard_pass=1; -if(! function_exists('install_init')) { + function install_init(&$a){ // $baseurl/install/testrwrite to test if rewite in .htaccess is working @@ -11,21 +11,20 @@ function install_init(&$a){ echo "ok"; killme(); } - + // We overwrite current theme css, because during install we could not have a working mod_rewrite // so we could not have a css at all. Here we set a static css file for the install procedure pages $a->config['system']['theme'] = "../install"; $a->theme['stylesheet'] = $a->get_baseurl()."/view/install/style.css"; - - - + + + global $install_wizard_pass; if (x($_POST,'pass')) $install_wizard_pass = intval($_POST['pass']); -} + } -if(! function_exists('install_post')) { function install_post(&$a) { global $install_wizard_pass, $db; @@ -113,18 +112,14 @@ function install_post(&$a) { break; } } -} -if(! function_exists('get_db_errno')) { function get_db_errno() { if(class_exists('mysqli')) return mysqli_connect_errno(); else return mysql_errno(); } -} -if(! function_exists('install_content')) { function install_content(&$a) { global $install_wizard_pass, $db; @@ -309,7 +304,6 @@ function install_content(&$a) { } } -} /** * checks : array passed to template @@ -318,8 +312,7 @@ function install_content(&$a) { * required : boolean * help : string optional */ -if(! function_exists('check_add')) { -function check_add(&$checks, $title, $status, $required, $help) { +function check_add(&$checks, $title, $status, $required, $help){ $checks[] = array( 'title' => $title, 'status' => $status, @@ -327,9 +320,7 @@ function check_add(&$checks, $title, $status, $required, $help) { 'help' => $help, ); } -} -if(! function_exists('check_php')) { function check_php(&$phpath, &$checks) { $passed = $passed2 = $passed3 = false; if (strlen($phpath)){ @@ -379,10 +370,9 @@ function check_php(&$phpath, &$checks) { check_add($checks, t('PHP register_argc_argv'), $passed3, true, $help); } -} + } -if(! function_exists('check_keys')) { function check_keys(&$checks) { $help = ''; @@ -402,10 +392,10 @@ function check_keys(&$checks) { $help .= t('If running under Windows, please see "http://www.php.net/manual/en/openssl.installation.php".'); } check_add($checks, t('Generate encryption keys'), $res, true, $help); -} + } -if(! function_exists('check_funcs')) { + function check_funcs(&$checks) { $ck_funcs = array(); check_add($ck_funcs, t('libCurl PHP module'), true, true, ""); @@ -467,9 +457,8 @@ function check_funcs(&$checks) { /*if((x($_SESSION,'sysmsg')) && is_array($_SESSION['sysmsg']) && count($_SESSION['sysmsg'])) notice( t('Please see the file "INSTALL.txt".') . EOL);*/ } -} -if(! function_exists('check_htconfig')) { + function check_htconfig(&$checks) { $status = true; $help = ""; @@ -484,10 +473,9 @@ function check_htconfig(&$checks) { } check_add($checks, t('.htconfig.php is writable'), $status, false, $help); -} + } -if(! function_exists('check_smarty3')) { function check_smarty3(&$checks) { $status = true; $help = ""; @@ -501,10 +489,9 @@ function check_smarty3(&$checks) { } check_add($checks, t('view/smarty3 is writable'), $status, true, $help); -} + } -if(! function_exists('check_htaccess')) { function check_htaccess(&$checks) { $a = get_app(); $status = true; @@ -524,9 +511,7 @@ function check_htaccess(&$checks) { // cannot check modrewrite if libcurl is not installed } } -} -if(! function_exists('check_imagik')) { function check_imagik(&$checks) { $imagick = false; $gif = false; @@ -543,18 +528,16 @@ function check_imagik(&$checks) { check_add($checks, t('ImageMagick supports GIF'), $gif, false, ""); } } -} -if(! function_exists('manual_config')) { + + function manual_config(&$a) { $data = htmlentities($a->data['txt'],ENT_COMPAT,'UTF-8'); $o = t('The database configuration file ".htconfig.php" could not be written. Please use the enclosed text to create a configuration file in your web server root.'); $o .= "<textarea rows=\"24\" cols=\"80\" >$data</textarea>"; return $o; } -} -if(! function_exists('load_database_rem')) { function load_database_rem($v, $i){ $l = trim($i); if (strlen($l)>1 && ($l[0]=="-" || ($l[0]=="/" && $l[1]=="*"))){ @@ -563,9 +546,8 @@ function load_database_rem($v, $i){ return $v."\n".$i; } } -} -if(! function_exists('load_database')) { + function load_database($db) { require_once("include/dbstructure.php"); @@ -585,9 +567,7 @@ function load_database($db) { return $errors; } -} -if(! function_exists('what_next')) { function what_next() { $a = get_app(); $baseurl = $a->get_baseurl(); @@ -599,4 +579,5 @@ function what_next() { .t("Go to your new Friendica node <a href='$baseurl/register'>registration page</a> and register as new user. Remember to use the same email you have entered as administrator email. This will allow you to enter the site admin panel.") ."</p>"; } -} + + diff --git a/mod/invite.php b/mod/invite.php index 1f559dabc0..ccf876c7c0 100644 --- a/mod/invite.php +++ b/mod/invite.php @@ -9,7 +9,6 @@ require_once('include/email.php'); -if(! function_exists('invite_post')) { function invite_post(&$a) { if(! local_user()) { @@ -50,7 +49,7 @@ function invite_post(&$a) { notice( sprintf( t('%s : Not a valid email address.'), $recip) . EOL); continue; } - + if($invonly && ($x || is_site_admin())) { $code = autoname(8) . srand(1000,9999); $nmessage = str_replace('$invite_code',$code,$message); @@ -71,8 +70,8 @@ function invite_post(&$a) { else $nmessage = $message; - $res = mail($recip, email_header_encode( t('Please join us on Friendica'),'UTF-8'), - $nmessage, + $res = mail($recip, email_header_encode( t('Please join us on Friendica'),'UTF-8'), + $nmessage, "From: " . $a->user['email'] . "\n" . 'Content-type: text/plain; charset=UTF-8' . "\n" . 'Content-transfer-encoding: 8bit' ); @@ -94,9 +93,8 @@ function invite_post(&$a) { notice( sprintf( tt("%d message sent.", "%d messages sent.", $total) , $total) . EOL); return; } -} -if(! function_exists('invite_content')) { + function invite_content(&$a) { if(! local_user()) { @@ -136,7 +134,7 @@ function invite_content(&$a) { '$msg_text' => t('Your message:'), '$default_message' => t('You are cordially invited to join me and other close friends on Friendica - and help us to create a better social web.') . "\r\n" . "\r\n" . $linktxt - . "\r\n" . "\r\n" . (($invonly) ? t('You will need to supply this invitation code: $invite_code') . "\r\n" . "\r\n" : '') .t('Once you have registered, please connect with me via my profile page at:') + . "\r\n" . "\r\n" . (($invonly) ? t('You will need to supply this invitation code: $invite_code') . "\r\n" . "\r\n" : '') .t('Once you have registered, please connect with me via my profile page at:') . "\r\n" . "\r\n" . $a->get_baseurl() . '/profile/' . $a->user['nickname'] . "\r\n" . "\r\n" . t('For more information about the Friendica project and why we feel it is important, please visit http://friendica.com') . "\r\n" . "\r\n" , '$submit' => t('Submit') @@ -144,4 +142,3 @@ function invite_content(&$a) { return $o; } -} diff --git a/mod/item.php b/mod/item.php index f8f2e0fafe..8c5a479646 100644 --- a/mod/item.php +++ b/mod/item.php @@ -25,7 +25,6 @@ require_once('include/text.php'); require_once('include/items.php'); require_once('include/Scrape.php'); -if(! function_exists('item_post')) { function item_post(&$a) { if((! local_user()) && (! remote_user()) && (! x($_REQUEST,'commenter'))) @@ -1018,9 +1017,7 @@ function item_post(&$a) { item_post_return($a->get_baseurl(), $api_source, $return_path); // NOTREACHED } -} -if(! function_exists('item_post_return')) { function item_post_return($baseurl, $api_source, $return_path) { // figure out how to return, depending on from whence we came @@ -1040,9 +1037,9 @@ function item_post_return($baseurl, $api_source, $return_path) { echo json_encode($json); killme(); } -} -if(! function_exists('item_content')) { + + function item_content(&$a) { if((! local_user()) && (! remote_user())) @@ -1061,7 +1058,6 @@ function item_content(&$a) { } return $o; } -} /** * This function removes the tag $tag from the text $body and replaces it with @@ -1075,7 +1071,6 @@ function item_content(&$a) { * * @return boolean true if replaced, false if not replaced */ -if(! function_exists('handle_tag')) { function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $network = "") { require_once("include/Scrape.php"); require_once("include/socgraph.php"); @@ -1250,9 +1245,8 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo return array('replaced' => $replaced, 'contact' => $r[0]); } -} -if(! function_exists('store_diaspora_comment_sig')) { + function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item, $post_id) { // We won't be able to sign Diaspora comments for authenticated visitors - we don't have their private key @@ -1290,4 +1284,3 @@ function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item, return; } -} diff --git a/mod/like.php b/mod/like.php index ef483a1f9e..8d383b9abe 100755 --- a/mod/like.php +++ b/mod/like.php @@ -5,7 +5,6 @@ require_once('include/bbcode.php'); require_once('include/items.php'); require_once('include/like.php'); -if(! function_exists('like_content')) { function like_content(&$a) { if(! local_user() && ! remote_user()) { return false; @@ -29,11 +28,11 @@ function like_content(&$a) { killme(); // NOTREACHED // return; // NOTREACHED } -} + // Decide how to return. If we were called with a 'return' argument, // then redirect back to the calling page. If not, just quietly end -if(! function_exists('like_content_return')) { + function like_content_return($baseurl, $return_path) { if($return_path) { @@ -46,4 +45,4 @@ function like_content_return($baseurl, $return_path) { killme(); } -} + diff --git a/mod/localtime.php b/mod/localtime.php index fc500f4dd9..d1453bc527 100644 --- a/mod/localtime.php +++ b/mod/localtime.php @@ -2,7 +2,7 @@ require_once('include/datetime.php'); -if(! function_exists('localtime_post')) { + function localtime_post(&$a) { $t = $_REQUEST['time']; @@ -13,10 +13,9 @@ function localtime_post(&$a) { if($_POST['timezone']) $a->data['mod-localtime'] = datetime_convert('UTC',$_POST['timezone'],$t,$bd_format); -} + } -if(! function_exists('localtime_content')) { function localtime_content(&$a) { $t = $_REQUEST['time']; if(! $t) @@ -39,12 +38,12 @@ function localtime_content(&$a) { $o .= '<form action ="' . $a->get_baseurl() . '/localtime?f=&time=' . $t . '" method="post" >'; - $o .= '<p>' . t('Please select your timezone:') . '</p>'; + $o .= '<p>' . t('Please select your timezone:') . '</p>'; $o .= select_timezone(($_REQUEST['timezone']) ? $_REQUEST['timezone'] : 'America/Los_Angeles'); $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form>'; return $o; -} -} + +} \ No newline at end of file diff --git a/mod/lockview.php b/mod/lockview.php index 82f93f4985..0ae54c8c12 100644 --- a/mod/lockview.php +++ b/mod/lockview.php @@ -1,8 +1,8 @@ <?php -if(! function_exists('lockview_content')) { -function lockview_content(&$a) { +function lockview_content(&$a) { + $type = (($a->argc > 1) ? $a->argv[1] : 0); if (is_numeric($type)) { $item_id = intval($type); @@ -10,13 +10,13 @@ function lockview_content(&$a) { } else { $item_id = (($a->argc > 2) ? intval($a->argv[2]) : 0); } - + if(! $item_id) killme(); if (!in_array($type, array('item','photo','event'))) killme(); - + $r = q("SELECT * FROM `%s` WHERE `id` = %d LIMIT 1", dbesc($type), intval($item_id) @@ -33,7 +33,7 @@ function lockview_content(&$a) { } - if(($item['private'] == 1) && (! strlen($item['allow_cid'])) && (! strlen($item['allow_gid'])) + if(($item['private'] == 1) && (! strlen($item['allow_cid'])) && (! strlen($item['allow_gid'])) && (! strlen($item['deny_cid'])) && (! strlen($item['deny_gid']))) { echo t('Remote privacy information not available.') . '<br />'; @@ -53,7 +53,7 @@ function lockview_content(&$a) { dbesc(implode(', ', $allowed_groups)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = '<b>' . $rr['name'] . '</b>'; } if(count($allowed_users)) { @@ -61,7 +61,7 @@ function lockview_content(&$a) { dbesc(implode(', ',$allowed_users)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = $rr['name']; } @@ -71,7 +71,7 @@ function lockview_content(&$a) { dbesc(implode(', ', $deny_groups)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = '<b><strike>' . $rr['name'] . '</strike></b>'; } if(count($deny_users)) { @@ -79,12 +79,12 @@ function lockview_content(&$a) { dbesc(implode(', ',$deny_users)) ); if(count($r)) - foreach($r as $rr) + foreach($r as $rr) $l[] = '<strike>' . $rr['name'] . '</strike>'; } echo $o . implode(', ', $l); killme(); -} + } diff --git a/mod/login.php b/mod/login.php index 47c329eb63..d09fc1868f 100644 --- a/mod/login.php +++ b/mod/login.php @@ -1,5 +1,5 @@ <?php -if(! function_exists('login_content')) { + function login_content(&$a) { if(x($_SESSION,'theme')) unset($_SESSION['theme']); @@ -9,5 +9,5 @@ function login_content(&$a) { if(local_user()) goaway(z_root()); return login(($a->config['register_policy'] == REGISTER_CLOSED) ? false : true); -} + } diff --git a/mod/lostpass.php b/mod/lostpass.php index 0c4bb1a833..938d1cbb00 100644 --- a/mod/lostpass.php +++ b/mod/lostpass.php @@ -4,7 +4,6 @@ require_once('include/email.php'); require_once('include/enotify.php'); require_once('include/text.php'); -if(! function_exists('lostpass_post')) { function lostpass_post(&$a) { $loginame = notags(trim($_POST['login-name'])); @@ -75,10 +74,10 @@ function lostpass_post(&$a) { 'body' => $body)); goaway(z_root()); -} + } -if(! function_exists('lostpass_content')) { + function lostpass_content(&$a) { @@ -165,5 +164,5 @@ function lostpass_content(&$a) { return $o; } -} + } diff --git a/mod/maintenance.php b/mod/maintenance.php index 02de29108f..b50c94c9b9 100644 --- a/mod/maintenance.php +++ b/mod/maintenance.php @@ -1,8 +1,7 @@ <?php -if(! function_exists('maintenance_content')) { + function maintenance_content(&$a) { return replace_macros(get_markup_template('maintenance.tpl'), array( '$sysdown' => t('System down for maintenance') )); } -} diff --git a/mod/manage.php b/mod/manage.php index 6af3db9971..adcc3d787a 100644 --- a/mod/manage.php +++ b/mod/manage.php @@ -2,7 +2,7 @@ require_once("include/text.php"); -if(! function_exists('manage_post')) { + function manage_post(&$a) { if(! local_user()) @@ -87,9 +87,9 @@ function manage_post(&$a) { goaway( $a->get_baseurl() . "/profile/" . $a->user['nickname'] ); // NOTREACHED } -} -if(! function_exists('manage_content')) { + + function manage_content(&$a) { if(! local_user()) { @@ -144,5 +144,5 @@ function manage_content(&$a) { )); return $o; -} + } diff --git a/mod/match.php b/mod/match.php index f4936b28dc..3b0367b429 100644 --- a/mod/match.php +++ b/mod/match.php @@ -13,7 +13,6 @@ require_once('mod/proxy.php'); * @param App &$a * @return void|string */ -if(! function_exists('match_content')) { function match_content(&$a) { $o = ''; @@ -110,4 +109,3 @@ function match_content(&$a) { return $o; } -} diff --git a/mod/message.php b/mod/message.php index 1f11797d8b..1724ebc424 100644 --- a/mod/message.php +++ b/mod/message.php @@ -3,7 +3,6 @@ require_once('include/acl_selectors.php'); require_once('include/message.php'); -if(! function_exists('message_init')) { function message_init(&$a) { $tabs = ''; @@ -37,10 +36,9 @@ function message_init(&$a) { '$baseurl' => $a->get_baseurl(true), '$base' => $base )); -} + } -if(! function_exists('message_post')) { function message_post(&$a) { if(! local_user()) { @@ -93,7 +91,7 @@ function message_post(&$a) { } else goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); -} + } // Note: the code in 'item_extract_images' and 'item_redir_and_replace_images' @@ -173,7 +171,7 @@ function item_redir_and_replace_images($body, $images, $cid) { }} -if(! function_exists('message_content')) { + function message_content(&$a) { $o = ''; @@ -532,9 +530,7 @@ function message_content(&$a) { return $o; } } -} -if(! function_exists('get_messages')) { function get_messages($user, $lstart, $lend) { return q("SELECT max(`mail`.`created`) AS `mailcreated`, min(`mail`.`seen`) AS `mailseen`, @@ -545,9 +541,7 @@ function get_messages($user, $lstart, $lend) { intval($user), intval($lstart), intval($lend) ); } -} -if(! function_exists('render_messages')) { function render_messages($msg, $t) { $a = get_app(); @@ -599,4 +593,3 @@ function render_messages($msg, $t) { return $rslt; } -} diff --git a/mod/modexp.php b/mod/modexp.php index 282d55a24b..bba2c2882d 100644 --- a/mod/modexp.php +++ b/mod/modexp.php @@ -2,7 +2,6 @@ require_once('library/asn1.php'); -if(! function_exists('modexp_init')) { function modexp_init(&$a) { if($a->argc != 2) @@ -30,5 +29,6 @@ function modexp_init(&$a) { echo 'RSA' . '.' . $m . '.' . $e ; killme(); + } -} + diff --git a/mod/mood.php b/mod/mood.php index 2476f06562..eee11e20c5 100644 --- a/mod/mood.php +++ b/mod/mood.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); -if(! function_exists('mood_init')) { + function mood_init(&$a) { if(! local_user()) @@ -59,7 +59,7 @@ function mood_init(&$a) { $uri = item_new_uri($a->get_hostname(),$uid); - $action = sprintf( t('%1$s is currently %2$s'), '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' , $verbs[$verb]); + $action = sprintf( t('%1$s is currently %2$s'), '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' , $verbs[$verb]); $arr = array(); @@ -105,9 +105,9 @@ function mood_init(&$a) { return; } -} -if(! function_exists('mood_content')) { + + function mood_content(&$a) { if(! local_user()) { @@ -138,5 +138,5 @@ function mood_content(&$a) { )); return $o; -} + } diff --git a/mod/msearch.php b/mod/msearch.php index 3b1b0b617a..89de5b7057 100644 --- a/mod/msearch.php +++ b/mod/msearch.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('msearch_post')) { function msearch_post(&$a) { $perpage = (($_POST['n']) ? $_POST['n'] : 80); @@ -27,8 +26,8 @@ function msearch_post(&$a) { if(count($r)) { foreach($r as $rr) $results[] = array( - 'name' => $rr['name'], - 'url' => $a->get_baseurl() . '/profile/' . $rr['nickname'], + 'name' => $rr['name'], + 'url' => $a->get_baseurl() . '/profile/' . $rr['nickname'], 'photo' => $a->get_baseurl() . '/photo/avatar/' . $rr['uid'] . '.jpg', 'tags' => str_replace(array(',',' '),array(' ',' '),$rr['pub_keywords']) ); @@ -39,5 +38,5 @@ function msearch_post(&$a) { echo json_encode($output); killme(); -} -} + +} \ No newline at end of file diff --git a/mod/navigation.php b/mod/navigation.php index 8fbabfda96..5db69b171e 100644 --- a/mod/navigation.php +++ b/mod/navigation.php @@ -2,7 +2,6 @@ require_once("include/nav.php"); -if(! function_exists('navigation_content')) { function navigation_content(&$a) { $nav_info = nav_info($a); @@ -23,5 +22,5 @@ function navigation_content(&$a) { '$apps' => $a->apps, '$clear_notifs' => t('Clear notifications') )); -} + } diff --git a/mod/network.php b/mod/network.php index f9a0bec238..0010a3d824 100644 --- a/mod/network.php +++ b/mod/network.php @@ -1,6 +1,4 @@ <?php - -if(! function_exists('network_init')) { function network_init(&$a) { if(! local_user()) { notice( t('Permission denied.') . EOL); @@ -155,10 +153,9 @@ function network_init(&$a) { $a->page['aside'] .= networks_widget($a->get_baseurl(true) . '/network',(x($_GET, 'nets') ? $_GET['nets'] : '')); $a->page['aside'] .= saved_searches($search); $a->page['aside'] .= fileas_widget($a->get_baseurl(true) . '/network',(x($_GET, 'file') ? $_GET['file'] : '')); -} + } -if(! function_exists('saved_searches')) { function saved_searches($search) { if(! feature_enabled(local_user(),'savedsearch')) @@ -207,7 +204,7 @@ function saved_searches($search) { )); return $o; -} + } /** @@ -225,7 +222,6 @@ function saved_searches($search) { * * @return Array ( $no_active, $comment_active, $postord_active, $conv_active, $new_active, $starred_active, $bookmarked_active, $spam_active ); */ -if(! function_exists('network_query_get_sel_tab')) { function network_query_get_sel_tab($a) { $no_active=''; $starred_active = ''; @@ -282,12 +278,10 @@ function network_query_get_sel_tab($a) { return array($no_active, $all_active, $postord_active, $conv_active, $new_active, $starred_active, $bookmarked_active, $spam_active); } -} /** * Return selected network from query */ -if(! function_exists('network_query_get_sel_net')) { function network_query_get_sel_net() { $network = false; @@ -297,9 +291,7 @@ function network_query_get_sel_net() { return $network; } -} -if(! function_exists('network_query_get_sel_group')) { function network_query_get_sel_group($a) { $group = false; @@ -309,9 +301,8 @@ function network_query_get_sel_group($a) { return $group; } -} -if(! function_exists('network_content')) { + function network_content(&$a, $update = 0) { require_once('include/conversation.php'); @@ -895,4 +886,4 @@ function network_content(&$a, $update = 0) { return $o; } -} + diff --git a/mod/newmember.php b/mod/newmember.php index ef25333302..aa55c3a098 100644 --- a/mod/newmember.php +++ b/mod/newmember.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('newmember_content')) { function newmember_content(&$a) { @@ -16,7 +15,7 @@ function newmember_content(&$a) { $o .= '<ul>'; - $o .= '<li> ' . '<a target="newmember" href="help/guide">' . t('Friendica Walk-Through') . '</a><br />' . t('On your <em>Quick Start</em> page - find a brief introduction to your profile and network tabs, make some new connections, and find some groups to join.') . '</li>' . EOL; + $o .= '<li> ' . '<a target="newmember" href="help/guide">' . t('Friendica Walk-Through') . '</a><br />' . t('On your <em>Quick Start</em> page - find a brief introduction to your profile and network tabs, make some new connections, and find some groups to join.') . '</li>' . EOL; $o .= '</ul>'; @@ -24,7 +23,7 @@ function newmember_content(&$a) { $o .= '<ul>'; - $o .= '<li>' . '<a target="newmember" href="settings">' . t('Go to Your Settings') . '</a><br />' . t('On your <em>Settings</em> page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web.') . '</li>' . EOL; + $o .= '<li>' . '<a target="newmember" href="settings">' . t('Go to Your Settings') . '</a><br />' . t('On your <em>Settings</em> page - change your initial password. Also make a note of your Identity Address. This looks just like an email address - and will be useful in making friends on the free social web.') . '</li>' . EOL; $o .= '<li>' . t('Review the other settings, particularly the privacy settings. An unpublished directory listing is like having an unlisted phone number. In general, you should probably publish your listing - unless all of your friends and potential friends know exactly how to find you.') . '</li>' . EOL; @@ -34,7 +33,7 @@ function newmember_content(&$a) { $o .= '<ul>'; - $o .= '<li>' . '<a target="newmember" href="profile_photo">' . t('Upload Profile Photo') . '</a><br />' . t('Upload a profile photo if you have not done so already. Studies have shown that people with real photos of themselves are ten times more likely to make friends than people who do not.') . '</li>' . EOL; + $o .= '<li>' . '<a target="newmember" href="profile_photo">' . t('Upload Profile Photo') . '</a><br />' . t('Upload a profile photo if you have not done so already. Studies have shown that people with real photos of themselves are ten times more likely to make friends than people who do not.') . '</li>' . EOL; $o .= '<li>' . '<a target="newmember" href="profiles">' . t('Edit Your Profile') . '</a><br />' . t('Edit your <strong>default</strong> profile to your liking. Review the settings for hiding your list of friends and hiding the profile from unknown visitors.') . '</li>' . EOL; @@ -47,7 +46,7 @@ function newmember_content(&$a) { $o .= '<ul>'; $mail_disabled = ((function_exists('imap_open') && (! get_config('system','imap_disabled'))) ? 0 : 1); - + if(! $mail_disabled) $o .= '<li>' . '<a target="newmember" href="settings/connectors">' . t('Importing Emails') . '</a><br />' . t('Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX') . '</li>' . EOL; @@ -83,4 +82,3 @@ function newmember_content(&$a) { return $o; } -} diff --git a/mod/nodeinfo.php b/mod/nodeinfo.php index 7f8939182e..ba310a1051 100644 --- a/mod/nodeinfo.php +++ b/mod/nodeinfo.php @@ -1,13 +1,12 @@ <?php /** * @file mod/nodeinfo.php - * + * * Documentation: http://nodeinfo.diaspora.software/schema.html */ require_once("include/plugin.php"); -if(! function_exists('nodeinfo_wellknown')) { function nodeinfo_wellknown(&$a) { if (!get_config("system", "nodeinfo")) { http_status_exit(404); @@ -20,9 +19,7 @@ function nodeinfo_wellknown(&$a) { echo json_encode($nodeinfo, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); exit; } -} -if(! function_exists('nodeinfo_init')) { function nodeinfo_init(&$a){ if (!get_config("system", "nodeinfo")) { http_status_exit(404); @@ -146,9 +143,9 @@ function nodeinfo_init(&$a){ echo json_encode($nodeinfo, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); exit; } -} -if(! function_exists('nodeinfo_cron')) { + + function nodeinfo_cron() { $a = get_app(); @@ -263,5 +260,5 @@ function nodeinfo_cron() { logger("cron_end"); set_config('nodeinfo','last_calucation', time()); } -} + ?> diff --git a/mod/nogroup.php b/mod/nogroup.php index 818b0da77a..9f6e978433 100644 --- a/mod/nogroup.php +++ b/mod/nogroup.php @@ -4,7 +4,6 @@ require_once('include/Contact.php'); require_once('include/socgraph.php'); require_once('include/contact_selectors.php'); -if(! function_exists('nogroup_init')) { function nogroup_init(&$a) { if(! local_user()) @@ -18,9 +17,8 @@ function nogroup_init(&$a) { $a->page['aside'] .= group_side('contacts','group','extended',0,$contact_id); } -} -if(! function_exists('nogroup_content')) { + function nogroup_content(&$a) { if(! local_user()) { @@ -68,5 +66,5 @@ function nogroup_content(&$a) { )); return $o; -} + } diff --git a/mod/noscrape.php b/mod/noscrape.php index 49fe2b9a37..51bd7234cf 100644 --- a/mod/noscrape.php +++ b/mod/noscrape.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('noscrape_init')) { function noscrape_init(&$a) { if($a->argc > 1) @@ -63,5 +62,5 @@ function noscrape_init(&$a) { header('Content-type: application/json; charset=utf-8'); echo json_encode($json_info); exit; -} + } diff --git a/mod/notes.php b/mod/notes.php index 7817e25547..73c1507e3e 100644 --- a/mod/notes.php +++ b/mod/notes.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('notes_init')) { function notes_init(&$a) { if(! local_user()) @@ -13,10 +12,10 @@ function notes_init(&$a) { nav_set_selected('home'); // profile_load($a,$which,$profile); -} + } -if(! function_exists('notes_content')) { + function notes_content(&$a,$update = false) { if(! local_user()) { @@ -70,12 +69,12 @@ function notes_content(&$a,$update = false) { // Construct permissions // default permissions - anonymous user - + $sql_extra = " AND `allow_cid` = '<" . $a->contact['id'] . ">' "; $r = q("SELECT COUNT(*) AS `total` FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 + WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 AND `item`.`type` = 'note' AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 AND `contact`.`self` = 1 AND `item`.`id` = `item`.`parent` AND `item`.`wall` = 0 @@ -91,7 +90,7 @@ function notes_content(&$a,$update = false) { $r = q("SELECT `item`.`id` AS `item_id`, `contact`.`uid` AS `contact-uid` FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 + WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 AND `item`.`type` = 'note' AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 AND `contact`.`self` = 1 AND `item`.`id` = `item`.`parent` AND `item`.`wall` = 0 @@ -110,10 +109,10 @@ function notes_content(&$a,$update = false) { foreach($r as $rr) $parents_arr[] = $rr['item_id']; $parents_str = implode(', ', $parents_arr); - - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`alias`, `contact`.`network`, `contact`.`rel`, - `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, + + $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, + `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`alias`, `contact`.`network`, `contact`.`rel`, + `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 @@ -136,4 +135,3 @@ function notes_content(&$a,$update = false) { $o .= paginate($a); return $o; } -} diff --git a/mod/notice.php b/mod/notice.php index a42d60dd40..19cf53189a 100644 --- a/mod/notice.php +++ b/mod/notice.php @@ -1,8 +1,7 @@ <?php -/* identi.ca -> friendica items permanent-url compatibility */ - -if(! function_exists('notice_init')) { - function notice_init(&$a) { + /* identi.ca -> friendica items permanent-url compatibility */ + + function notice_init(&$a){ $id = $a->argv[1]; $r = q("SELECT user.nickname FROM user LEFT JOIN item ON item.uid=user.uid WHERE item.id=%d", intval($id) @@ -17,5 +16,5 @@ if(! function_exists('notice_init')) { } return; + } -} diff --git a/mod/notifications.php b/mod/notifications.php index c7421b2d42..a267b7c958 100644 --- a/mod/notifications.php +++ b/mod/notifications.php @@ -3,7 +3,6 @@ include_once("include/bbcode.php"); include_once("include/contact_selectors.php"); include_once("include/Scrape.php"); -if(! function_exists('notifications_post')) { function notifications_post(&$a) { if(! local_user()) { @@ -59,11 +58,11 @@ function notifications_post(&$a) { } } } -} -if(! function_exists('notifications_content')) { + + function notifications_content(&$a) { if(! local_user()) { @@ -580,4 +579,3 @@ function notifications_content(&$a) { $o .= paginate($a); return $o; } -} diff --git a/mod/notify.php b/mod/notify.php index 7acac1084a..02260514af 100644 --- a/mod/notify.php +++ b/mod/notify.php @@ -1,6 +1,6 @@ <?php -if(! function_exists('notify_init')) { + function notify_init(&$a) { if(! local_user()) return; @@ -42,10 +42,10 @@ function notify_init(&$a) { echo $j; killme(); } -} + } -if(! function_exists('notify_content')) { + function notify_content(&$a) { if(! local_user()) return login(); @@ -80,5 +80,5 @@ function notify_content(&$a) { return $o; -} + } diff --git a/mod/oembed.php b/mod/oembed.php index 021cbab6fd..cb478cb860 100644 --- a/mod/oembed.php +++ b/mod/oembed.php @@ -1,8 +1,7 @@ <?php require_once("include/oembed.php"); -if(! function_exists('oembed_content')) { -function oembed_content(&$a) { +function oembed_content(&$a){ // logger('mod_oembed ' . $a->query_string, LOGGER_ALL); if ($a->argv[1]=='b2h'){ @@ -34,4 +33,3 @@ function oembed_content(&$a) { } killme(); } -} diff --git a/mod/oexchange.php b/mod/oexchange.php index 1e7c9b23c9..bbb436e702 100644 --- a/mod/oexchange.php +++ b/mod/oexchange.php @@ -1,6 +1,6 @@ <?php -if(! function_exists('oexchange_init')) { + function oexchange_init(&$a) { if(($a->argc > 1) && ($a->argv[1] === 'xrd')) { @@ -11,10 +11,9 @@ function oexchange_init(&$a) { killme(); } -} + } -if(! function_exists('oexchange_content')) { function oexchange_content(&$a) { if(! local_user()) { @@ -27,13 +26,13 @@ function oexchange_content(&$a) { return; } - $url = (((x($_REQUEST,'url')) && strlen($_REQUEST['url'])) + $url = (((x($_REQUEST,'url')) && strlen($_REQUEST['url'])) ? urlencode(notags(trim($_REQUEST['url']))) : ''); - $title = (((x($_REQUEST,'title')) && strlen($_REQUEST['title'])) + $title = (((x($_REQUEST,'title')) && strlen($_REQUEST['title'])) ? '&title=' . urlencode(notags(trim($_REQUEST['title']))) : ''); - $description = (((x($_REQUEST,'description')) && strlen($_REQUEST['description'])) + $description = (((x($_REQUEST,'description')) && strlen($_REQUEST['description'])) ? '&description=' . urlencode(notags(trim($_REQUEST['description']))) : ''); - $tags = (((x($_REQUEST,'tags')) && strlen($_REQUEST['tags'])) + $tags = (((x($_REQUEST,'tags')) && strlen($_REQUEST['tags'])) ? '&tags=' . urlencode(notags(trim($_REQUEST['tags']))) : ''); $s = fetch_url($a->get_baseurl() . '/parse_url?f=&url=' . $url . $title . $description . $tags); @@ -53,5 +52,7 @@ function oexchange_content(&$a) { $_REQUEST = $post; require_once('mod/item.php'); item_post($a); + } -} + + diff --git a/mod/openid.php b/mod/openid.php index a92a124c0d..5d5539f00e 100644 --- a/mod/openid.php +++ b/mod/openid.php @@ -1,8 +1,9 @@ <?php + require_once('library/openid.php'); -if(! function_exists('openid_content')) { + function openid_content(&$a) { $noid = get_config('system','no_openid'); @@ -24,8 +25,8 @@ function openid_content(&$a) { goaway(z_root()); } - $r = q("SELECT `user`.*, `user`.`pubkey` as `upubkey`, `user`.`prvkey` as `uprvkey` - FROM `user` WHERE `openid` = '%s' AND `blocked` = 0 + $r = q("SELECT `user`.*, `user`.`pubkey` as `upubkey`, `user`.`prvkey` as `uprvkey` + FROM `user` WHERE `openid` = '%s' AND `blocked` = 0 AND `account_expired` = 0 AND `account_removed` = 0 AND `verified` = 1 LIMIT 1", dbesc($authid) ); @@ -39,7 +40,7 @@ function openid_content(&$a) { require_once('include/security.php'); authenticate_success($r[0],true,true); - // just in case there was no return url set + // just in case there was no return url set // and we fell through goaway(z_root()); @@ -93,4 +94,3 @@ function openid_content(&$a) { goaway(z_root()); // NOTREACHED } -} diff --git a/mod/opensearch.php b/mod/opensearch.php index f3d55a1029..ff748d1c53 100644 --- a/mod/opensearch.php +++ b/mod/opensearch.php @@ -1,18 +1,18 @@ <?php -if(! function_exists('opensearch_content')) { - function opensearch_content(&$a) { + function opensearch_content(&$a) { + $tpl = get_markup_template('opensearch.tpl'); - + header("Content-type: application/opensearchdescription+xml"); - + $o = replace_macros($tpl, array( '$baseurl' => $a->get_baseurl(), '$nodename' => $a->get_hostname(), )); - + echo $o; - + killme(); + } -} -?> +?> \ No newline at end of file diff --git a/mod/ostatus_subscribe.php b/mod/ostatus_subscribe.php index a21436db49..6cca0bf679 100644 --- a/mod/ostatus_subscribe.php +++ b/mod/ostatus_subscribe.php @@ -3,7 +3,6 @@ require_once('include/Scrape.php'); require_once('include/follow.php'); -if(! function_exists('ostatus_subscribe_content')) { function ostatus_subscribe_content(&$a) { if(! local_user()) { @@ -77,4 +76,3 @@ function ostatus_subscribe_content(&$a) { return $o; } -} diff --git a/mod/p.php b/mod/p.php index 225b831fea..92b72dc1ce 100644 --- a/mod/p.php +++ b/mod/p.php @@ -4,8 +4,7 @@ This file is part of the Diaspora protocol. It is used for fetching single publi */ require_once("include/diaspora.php"); -if(! function_exists('p_init')) { -function p_init($a) { +function p_init($a){ if ($a->argc != 2) { header($_SERVER["SERVER_PROTOCOL"].' 510 '.t('Not Extended')); killme(); @@ -80,4 +79,3 @@ function p_init($a) { killme(); } -} diff --git a/mod/parse_url.php b/mod/parse_url.php index 481cb89361..a1ca5a3db5 100644 --- a/mod/parse_url.php +++ b/mod/parse_url.php @@ -1,14 +1,14 @@ <?php -/** +/** * @file mod/parse_url.php - * + * * @todo https://developers.google.com/+/plugins/snippet/ - * + * * @verbatim * <meta itemprop="name" content="Toller Titel"> * <meta itemprop="description" content="Eine tolle Beschreibung"> * <meta itemprop="image" content="http://maple.libertreeproject.org/images/tree-icon.png"> - * + * * <body itemscope itemtype="http://schema.org/Product"> * <h1 itemprop="name">Shiny Trinket</h1> * <img itemprop="image" src="{image-url}" /> @@ -27,7 +27,6 @@ if(!function_exists('deletenode')) { } } -if(! function_exists('completeurl')) { function completeurl($url, $scheme) { $urlarr = parse_url($url); @@ -54,9 +53,7 @@ function completeurl($url, $scheme) { return($complete); } -} -if(! function_exists('parseurl_getsiteinfo_cached')) { function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = true) { if ($url == "") @@ -80,9 +77,7 @@ function parseurl_getsiteinfo_cached($url, $no_guessing = false, $do_oembed = tr return $data; } -} -if(! function_exists('parseurl_getsiteinfo')) { function parseurl_getsiteinfo($url, $no_guessing = false, $do_oembed = true, $count = 1) { require_once("include/network.php"); require_once("include/Photo.php"); @@ -405,15 +400,11 @@ function parseurl_getsiteinfo($url, $no_guessing = false, $do_oembed = true, $co return($siteinfo); } -} -if(! function_exists('arr_add_hashes')) { function arr_add_hashes(&$item,$k) { $item = '#' . $item; } -} -if(! function_exists('parse_url_content')) { function parse_url_content(&$a) { $text = null; @@ -567,5 +558,4 @@ function parse_url_content(&$a) { killme(); } -} ?> diff --git a/mod/photo.php b/mod/photo.php index 3baff13db5..4166b4d539 100644 --- a/mod/photo.php +++ b/mod/photo.php @@ -3,7 +3,6 @@ require_once('include/security.php'); require_once('include/Photo.php'); -if(! function_exists('photo_init')) { function photo_init(&$a) { global $_SERVER; @@ -210,4 +209,3 @@ function photo_init(&$a) { killme(); // NOTREACHED } -} diff --git a/mod/photos.php b/mod/photos.php index 9821918e5e..a9dade6a81 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -9,7 +9,6 @@ require_once('include/redir.php'); require_once('include/tags.php'); require_once('include/threads.php'); -if(! function_exists('photos_init')) { function photos_init(&$a) { if($a->argc > 1) @@ -122,9 +121,9 @@ function photos_init(&$a) { return; } -} -if(! function_exists('photos_post')) { + + function photos_post(&$a) { logger('mod-photos: photos_post: begin' , LOGGER_DEBUG); @@ -958,9 +957,9 @@ function photos_post(&$a) { goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); // NOTREACHED } -} -if(! function_exists('photos_content')) { + + function photos_content(&$a) { // URLs: @@ -1329,7 +1328,7 @@ function photos_content(&$a) { } - /** + /** * Display one photo */ @@ -1862,7 +1861,7 @@ function photos_content(&$a) { //hide profile photos to others if((! $is_owner) && (! remote_user()) && ($rr['album'] == t('Profile Photos'))) continue; - + if($twist == 'rotright') $twist = 'rotleft'; else @@ -1907,4 +1906,4 @@ function photos_content(&$a) { $o .= paginate($a); return $o; } -} + diff --git a/mod/ping.php b/mod/ping.php index 0d1659a763..577a2c6c82 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -5,7 +5,6 @@ require_once('include/ForumManager.php'); require_once('include/group.php'); require_once("mod/proxy.php"); -if(! function_exists('ping_init')) { function ping_init(&$a) { header("Content-type: text/xml"); @@ -339,9 +338,7 @@ function ping_init(&$a) { killme(); } -} -if(! function_exists('ping_get_notifications')) { function ping_get_notifications($uid) { $result = array(); @@ -409,4 +406,3 @@ function ping_get_notifications($uid) { return($result); } -} diff --git a/mod/poco.php b/mod/poco.php index 4b04d70138..0a1b392169 100644 --- a/mod/poco.php +++ b/mod/poco.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('poco_init')) { function poco_init(&$a) { require_once("include/bbcode.php"); @@ -325,5 +324,5 @@ function poco_init(&$a) { else http_status_exit(500); -} + } diff --git a/mod/poke.php b/mod/poke.php index 1af78b68ed..45a577cda6 100644 --- a/mod/poke.php +++ b/mod/poke.php @@ -4,11 +4,11 @@ * * Poke, prod, finger, or otherwise do unspeakable things to somebody - who must be a connection in your address book * This function can be invoked with the required arguments (verb and cid and private and possibly parent) silently via ajax or - * other web request. You must be logged in and connected to a profile. + * other web request. You must be logged in and connected to a profile. * If the required arguments aren't present, we'll display a simple form to choose a recipient and a verb. * parent is a special argument which let's you attach this activity as a comment to an existing conversation, which * may have started with somebody else poking (etc.) somebody, but this isn't necessary. This can be used in the more pokes - * plugin version to have entire conversations where Alice poked Bob, Bob fingered Alice, Alice hugged Bob, etc. + * plugin version to have entire conversations where Alice poked Bob, Bob fingered Alice, Alice hugged Bob, etc. * * private creates a private conversation with the recipient. Otherwise your profile's default post privacy is used. * @@ -18,7 +18,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); -if(! function_exists('poke_init')) { + function poke_init(&$a) { if(! local_user()) @@ -140,9 +140,9 @@ function poke_init(&$a) { return; } -} -if(! function_exists('poke_content')) { + + function poke_content(&$a) { if(! local_user()) { @@ -201,5 +201,5 @@ function poke_content(&$a) { )); return $o; -} + } diff --git a/mod/post.php b/mod/post.php index 631bf0eba6..c0e783a6aa 100644 --- a/mod/post.php +++ b/mod/post.php @@ -9,8 +9,7 @@ require_once('include/salmon.php'); require_once('include/crypto.php'); // not yet ready for prime time //require_once('include/zot.php'); - -if(! function_exists('post_post')) { + function post_post(&$a) { $bulk_delivery = false; @@ -20,7 +19,7 @@ function post_post(&$a) { } else { $nickname = $a->argv[2]; - $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' + $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nickname) ); @@ -49,4 +48,4 @@ function post_post(&$a) { http_status_exit(($ret) ? $ret : 200); // NOTREACHED } -} + diff --git a/mod/pretheme.php b/mod/pretheme.php index 5d1c261fcb..4584cb29e2 100644 --- a/mod/pretheme.php +++ b/mod/pretheme.php @@ -1,8 +1,7 @@ <?php -if(! function_exists('pretheme_init')) { function pretheme_init(&$a) { - + if($_REQUEST['theme']) { $theme = $_REQUEST['theme']; $info = get_theme_info($theme); @@ -21,4 +20,3 @@ function pretheme_init(&$a) { } killme(); } -} diff --git a/mod/probe.php b/mod/probe.php index fcf83e7603..c95db291b3 100644 --- a/mod/probe.php +++ b/mod/probe.php @@ -2,14 +2,13 @@ require_once('include/Scrape.php'); -if(! function_exists('probe_content')) { function probe_content(&$a) { $o .= '<h3>Probe Diagnostic</h3>'; $o .= '<form action="probe" method="get">'; $o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] .'" />'; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; @@ -23,4 +22,3 @@ function probe_content(&$a) { } return $o; } -} diff --git a/mod/profile.php b/mod/profile.php index b02570d5af..26bd395230 100644 --- a/mod/profile.php +++ b/mod/profile.php @@ -3,7 +3,7 @@ require_once('include/contact_widgets.php'); require_once('include/redir.php'); -if(! function_exists('profile_init')) { + function profile_init(&$a) { if(! x($a->page,'aside')) @@ -65,10 +65,10 @@ function profile_init(&$a) { foreach($dfrn_pages as $dfrn) $a->page['htmlhead'] .= "<link rel=\"dfrn-{$dfrn}\" href=\"".$a->get_baseurl()."/dfrn_{$dfrn}/{$which}\" />\r\n"; $a->page['htmlhead'] .= "<link rel=\"dfrn-poco\" href=\"".$a->get_baseurl()."/poco/{$which}\" />\r\n"; -} + } -if(! function_exists('profile_content')) { + function profile_content(&$a, $update = 0) { $category = $datequery = $datequery2 = ''; @@ -350,4 +350,3 @@ function profile_content(&$a, $update = 0) { return $o; } -} diff --git a/mod/profile_photo.php b/mod/profile_photo.php index e3d6adb491..4e8d279a97 100644 --- a/mod/profile_photo.php +++ b/mod/profile_photo.php @@ -2,7 +2,6 @@ require_once("include/Photo.php"); -if(! function_exists('profile_photo_init')) { function profile_photo_init(&$a) { if(! local_user()) { @@ -10,10 +9,10 @@ function profile_photo_init(&$a) { } profile_load($a,$a->user['nickname']); -} + } -if(! function_exists('profile_photo_post')) { + function profile_photo_post(&$a) { if(! local_user()) { @@ -144,7 +143,7 @@ function profile_photo_post(&$a) { $filesize = intval($_FILES['userfile']['size']); $filetype = $_FILES['userfile']['type']; if ($filetype=="") $filetype=guess_image_type($filename); - + $maximagesize = get_config('system','maximagesize'); if(($maximagesize) && ($filesize > $maximagesize)) { @@ -165,7 +164,7 @@ function profile_photo_post(&$a) { $ph->orient($src); @unlink($src); return profile_photo_crop_ui_head($a, $ph); -} + } @@ -176,7 +175,7 @@ function profile_photo_content(&$a) { notice( t('Permission denied.') . EOL ); return; } - + $newuser = false; if($a->argc == 2 && $a->argv[1] === 'new') @@ -187,9 +186,9 @@ function profile_photo_content(&$a) { notice( t('Permission denied.') . EOL ); return; }; - + // check_form_security_token_redirectOnErr('/profile_photo', 'profile_photo'); - + $resource_id = $a->argv[2]; //die(":".local_user()); $r=q("SELECT * FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' ORDER BY `scale` ASC", @@ -241,7 +240,7 @@ function profile_photo_content(&$a) { if(! x($a->config,'imagecrop')) { - + $tpl = get_markup_template('profile_photo.tpl'); $o .= replace_macros($tpl,array( @@ -296,11 +295,11 @@ function profile_photo_crop_ui_head(&$a, $ph){ } $hash = photo_new_resource(); - + $smallest = 0; - $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 0 ); + $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 0 ); if($r) info( t('Image uploaded successfully.') . EOL ); @@ -309,8 +308,8 @@ function profile_photo_crop_ui_head(&$a, $ph){ if($width > 640 || $height > 640) { $ph->scaleImage(640); - $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 1 ); - + $r = $ph->store(local_user(), 0 , $hash, $filename, t('Profile Photos'), 1 ); + if($r === false) notice( sprintf(t('Image size reduction [%s] failed.'),"640") . EOL ); else @@ -324,3 +323,4 @@ function profile_photo_crop_ui_head(&$a, $ph){ $a->page['end'] .= replace_macros(get_markup_template("cropend.tpl"), array()); return; }} + diff --git a/mod/profiles.php b/mod/profiles.php index 9ce478ba19..5c372de8ee 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -1,7 +1,6 @@ <?php require_once("include/Contact.php"); -if(! function_exists('profiles_init')) { function profiles_init(&$a) { nav_set_selected('profiles'); @@ -140,10 +139,9 @@ function profiles_init(&$a) { } -} + } -if(! function_exists('profile_clean_keywords')) { function profile_clean_keywords($keywords) { $keywords = str_replace(","," ",$keywords); $keywords = explode(" ", $keywords); @@ -160,9 +158,7 @@ function profile_clean_keywords($keywords) { return $keywords; } -} -if(! function_exists('profiles_post')) { function profiles_post(&$a) { if(! local_user()) { @@ -506,9 +502,8 @@ function profiles_post(&$a) { } } } -} -if(! function_exists('profile_activity')) { + function profile_activity($changed, $value) { $a = get_app(); @@ -598,9 +593,8 @@ function profile_activity($changed, $value) { } } -} -if(! function_exists('profiles_content')) { + function profiles_content(&$a) { if(! local_user()) { @@ -824,5 +818,5 @@ function profiles_content(&$a) { } return $o; } -} + } diff --git a/mod/profperm.php b/mod/profperm.php index 6fb7172949..077f695bea 100644 --- a/mod/profperm.php +++ b/mod/profperm.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('profperm_init')) { function profperm_init(&$a) { if(! local_user()) @@ -10,10 +9,10 @@ function profperm_init(&$a) { $profile = $a->argv[1]; profile_load($a,$which,$profile); -} + } -if(! function_exists('profperm_content')) { + function profperm_content(&$a) { if(! local_user()) { @@ -109,9 +108,9 @@ function profperm_content(&$a) { } $o .= '<div id="prof-update-wrapper">'; - if($change) + if($change) $o = ''; - + $o .= '<div id="prof-members-title">'; $o .= '<h3>' . t('Visible To') . '</h3>'; $o .= '</div>'; @@ -157,5 +156,6 @@ function profperm_content(&$a) { } $o .= '</div>'; return $o; + } -} + diff --git a/mod/proxy.php b/mod/proxy.php index 8e2a389254..abcaf49127 100644 --- a/mod/proxy.php +++ b/mod/proxy.php @@ -12,7 +12,6 @@ define("PROXY_SIZE_LARGE", "large"); require_once('include/security.php'); require_once("include/Photo.php"); -if(! function_exists('proxy_init')) { function proxy_init() { global $a, $_SERVER; @@ -233,9 +232,7 @@ function proxy_init() { killme(); } -} -if(! function_exists('proxy_url')) { function proxy_url($url, $writemode = false, $size = "") { global $_SERVER; @@ -297,13 +294,11 @@ function proxy_url($url, $writemode = false, $size = "") { else return ($proxypath.$size); } -} /** * @param $url string * @return boolean */ -if(! function_exists('proxy_is_local_image')) { function proxy_is_local_image($url) { if ($url[0] == '/') return true; @@ -314,9 +309,7 @@ function proxy_is_local_image($url) { $url = normalise_link($url); return (substr($url, 0, strlen($baseurl)) == $baseurl); } -} -if(! function_exists('proxy_parse_query')) { function proxy_parse_query($var) { /** * Use this function to parse out the query array element from @@ -335,9 +328,7 @@ function proxy_parse_query($var) { unset($val, $x, $var); return $arr; } -} -if(! function_exists('proxy_img_cb')) { function proxy_img_cb($matches) { // if the picture seems to be from another picture cache then take the original source @@ -351,13 +342,10 @@ function proxy_img_cb($matches) { return $matches[1].proxy_url(htmlspecialchars_decode($matches[2])).$matches[3]; } -} -if(! function_exists('proxy_parse_html')) { function proxy_parse_html($html) { $a = get_app(); $html = str_replace(normalise_link($a->get_baseurl())."/", $a->get_baseurl()."/", $html); return preg_replace_callback("/(<img [^>]*src *= *[\"'])([^\"']+)([\"'][^>]*>)/siU", "proxy_img_cb", $html); } -} diff --git a/mod/pubsub.php b/mod/pubsub.php index 15523e637a..beb73b4e2c 100644 --- a/mod/pubsub.php +++ b/mod/pubsub.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('hub_return')) { function hub_return($valid,$body) { if($valid) { @@ -15,18 +14,18 @@ function hub_return($valid,$body) { // NOTREACHED } -} // when receiving an XML feed, always return OK -if(! function_exists('hub_post_return')) { + function hub_post_return() { + header($_SERVER["SERVER_PROTOCOL"] . ' 200 ' . 'OK'); killme(); -} + } -if(! function_exists('pubsub_init')) { + function pubsub_init(&$a) { $nick = (($a->argc > 1) ? notags(trim($a->argv[1])) : ''); @@ -58,7 +57,7 @@ function pubsub_init(&$a) { $sql_extra = ((strlen($hub_verify)) ? sprintf(" AND `hub-verify` = '%s' ", dbesc($hub_verify)) : ''); - $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d + $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d AND `blocked` = 0 AND `pending` = 0 $sql_extra LIMIT 1", intval($contact_id), intval($owner['uid']) @@ -76,7 +75,7 @@ function pubsub_init(&$a) { $contact = $r[0]; - // We must initiate an unsubscribe request with a verify_token. + // We must initiate an unsubscribe request with a verify_token. // Don't allow outsiders to unsubscribe us. if($hub_mode === 'unsubscribe') { @@ -96,11 +95,9 @@ function pubsub_init(&$a) { hub_return(true, $hub_challenge); } } -} require_once('include/security.php'); -if(! function_exists('pubsub_post')) { function pubsub_post(&$a) { $xml = file_get_contents('php://input'); @@ -158,5 +155,8 @@ function pubsub_post(&$a) { consume_feed($xml,$importer,$contact,$feedhub,1,2); hub_post_return(); + } -} + + + diff --git a/mod/pubsubhubbub.php b/mod/pubsubhubbub.php index b0e3ef3099..5d7621cc74 100644 --- a/mod/pubsubhubbub.php +++ b/mod/pubsubhubbub.php @@ -1,12 +1,9 @@ <?php -if(! function_exists('post_var')) { function post_var($name) { return (x($_POST, $name)) ? notags(trim($_POST[$name])) : ''; } -} -if(! function_exists('pubsubhubbub_init')) { function pubsubhubbub_init(&$a) { // PuSH subscription must be considered "public" so just block it // if public access isn't enabled. @@ -161,5 +158,5 @@ function pubsubhubbub_init(&$a) { killme(); } -} + ?> diff --git a/mod/qsearch.php b/mod/qsearch.php index cffc3e50ba..c35e253b67 100644 --- a/mod/qsearch.php +++ b/mod/qsearch.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('qsearch_init')) { function qsearch_init(&$a) { if(! local_user()) @@ -48,4 +47,4 @@ function qsearch_init(&$a) { echo json_encode((object) $results); killme(); } -} + diff --git a/mod/randprof.php b/mod/randprof.php index e9e0a8e7bb..6713a81d9e 100644 --- a/mod/randprof.php +++ b/mod/randprof.php @@ -1,6 +1,6 @@ <?php -if(! function_exists('randprof_init')) { + function randprof_init(&$a) { require_once('include/Contact.php'); $x = random_profile(); @@ -8,4 +8,3 @@ function randprof_init(&$a) { goaway(zrl($x)); goaway($a->get_baseurl() . '/profile'); } -} diff --git a/mod/receive.php b/mod/receive.php index 3a30058cdc..95a5101675 100644 --- a/mod/receive.php +++ b/mod/receive.php @@ -9,7 +9,7 @@ require_once('include/salmon.php'); require_once('include/crypto.php'); require_once('include/diaspora.php'); -if(! function_exists('receive_post')) { + function receive_post(&$a) { @@ -73,4 +73,4 @@ function receive_post(&$a) { http_status_exit(($ret) ? $ret : 200); // NOTREACHED } -} + diff --git a/mod/redir.php b/mod/redir.php index 2dda0571b2..632c395786 100644 --- a/mod/redir.php +++ b/mod/redir.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('redir_init')) { function redir_init(&$a) { $url = ((x($_GET,'url')) ? $_GET['url'] : ''); @@ -58,9 +57,9 @@ function redir_init(&$a) { intval(time() + 45) ); - logger('mod_redir: ' . $r[0]['name'] . ' ' . $sec, LOGGER_DEBUG); + logger('mod_redir: ' . $r[0]['name'] . ' ' . $sec, LOGGER_DEBUG); $dest = (($url) ? '&destination_url=' . $url : ''); - goaway ($r[0]['poll'] . '?dfrn_id=' . $dfrn_id + goaway ($r[0]['poll'] . '?dfrn_id=' . $dfrn_id . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&type=profile&sec=' . $sec . $dest . $quiet ); } @@ -76,4 +75,3 @@ function redir_init(&$a) { goaway(z_root()); } -} diff --git a/mod/regmod.php b/mod/regmod.php index 2e3ca414e3..5a90db1f90 100644 --- a/mod/regmod.php +++ b/mod/regmod.php @@ -3,7 +3,6 @@ require_once('include/enotify.php'); require_once('include/user.php'); -if(! function_exists('user_allow')) { function user_allow($hash) { $a = get_app(); @@ -56,14 +55,14 @@ function user_allow($hash) { info( t('Account approved.') . EOL ); return true; } -} + } // This does not have to go through user_remove() and save the nickname // permanently against re-registration, as the person was not yet // allowed to have friends on this system -if(! function_exists('user_deny')) { + function user_deny($hash) { $register = q("SELECT * FROM `register` WHERE `hash` = '%s' LIMIT 1", @@ -92,10 +91,9 @@ function user_deny($hash) { ); notice( sprintf(t('Registration revoked for %s'), $user[0]['username']) . EOL); return true; -} + } -if(! function_exists('regmod_content')) { function regmod_content(&$a) { global $lang; @@ -133,4 +131,3 @@ function regmod_content(&$a) { killme(); } } -} diff --git a/mod/removeme.php b/mod/removeme.php index 6c84c41892..904606fd57 100644 --- a/mod/removeme.php +++ b/mod/removeme.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('removeme_post')) { function removeme_post(&$a) { if(! local_user()) @@ -25,10 +24,9 @@ function removeme_post(&$a) { user_remove($a->user['uid']); // NOTREACHED } -} + } -if(! function_exists('removeme_content')) { function removeme_content(&$a) { if(! local_user()) @@ -52,5 +50,5 @@ function removeme_content(&$a) { )); return $o; -} + } diff --git a/mod/repair_ostatus.php b/mod/repair_ostatus.php index e3956ba8cb..2b1224f423 100755 --- a/mod/repair_ostatus.php +++ b/mod/repair_ostatus.php @@ -3,7 +3,6 @@ require_once('include/Scrape.php'); require_once('include/follow.php'); -if(! function_exists('repair_ostatus_content')) { function repair_ostatus_content(&$a) { if(! local_user()) { @@ -56,4 +55,3 @@ function repair_ostatus_content(&$a) { return $o; } -} diff --git a/mod/rsd_xml.php b/mod/rsd_xml.php index 6f9c209fab..f4984f0f0f 100644 --- a/mod/rsd_xml.php +++ b/mod/rsd_xml.php @@ -1,6 +1,7 @@ <?php -if(! function_exists('rsd_xml_content')) { + + function rsd_xml_content(&$a) { header ("Content-Type: text/xml"); echo '<?xml version="1.0" encoding="UTF-8"?> @@ -20,5 +21,4 @@ function rsd_xml_content(&$a) { </rsd> '; die(); -} -} +} \ No newline at end of file diff --git a/mod/salmon.php b/mod/salmon.php index ee3826d8a8..9c22e42d11 100644 --- a/mod/salmon.php +++ b/mod/salmon.php @@ -6,7 +6,6 @@ require_once('include/crypto.php'); require_once('include/items.php'); require_once('include/follow.php'); -if(! function_exists('salmon_return')) { function salmon_return($val) { if($val >= 400) @@ -17,10 +16,9 @@ function salmon_return($val) { logger('mod-salmon returns ' . $val); header($_SERVER["SERVER_PROTOCOL"] . ' ' . $val . ' ' . $err); killme(); -} + } -if(! function_exists('salmon_post')) { function salmon_post(&$a) { $xml = file_get_contents('php://input'); @@ -157,7 +155,7 @@ function salmon_post(&$a) { if(get_pconfig($importer['uid'],'system','ostatus_autofriend')) { $result = new_contact($importer['uid'],$author_link); if($result['success']) { - $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s') + $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s') AND `uid` = %d LIMIT 1", dbesc(NETWORK_OSTATUS), dbesc($author_link), @@ -187,4 +185,3 @@ function salmon_post(&$a) { http_status_exit(200); } -} diff --git a/mod/search.php b/mod/search.php index 431bd821d6..7c78339c70 100644 --- a/mod/search.php +++ b/mod/search.php @@ -4,7 +4,6 @@ require_once('include/security.php'); require_once('include/conversation.php'); require_once('mod/dirfind.php'); -if(! function_exists('search_saved_searches')) { function search_saved_searches() { $o = ''; @@ -40,10 +39,10 @@ function search_saved_searches() { } return $o; -} + } -if(! function_exists('search_init')) { + function search_init(&$a) { $search = ((x($_GET,'search')) ? notags(trim(rawurldecode($_GET['search']))) : ''); @@ -77,18 +76,17 @@ function search_init(&$a) { } -} + } -if(! function_exists('search_post')) { + function search_post(&$a) { if(x($_POST,'search')) $a->data['search'] = $_POST['search']; } -} -if(! function_exists('search_content')) { + function search_content(&$a) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -250,4 +248,4 @@ function search_content(&$a) { return $o; } -} + diff --git a/mod/session.php b/mod/session.php index ac3d885b63..22c855edba 100644 --- a/mod/session.php +++ b/mod/session.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('session_content')) { function session_content(&$a) { -} + } diff --git a/mod/settings.php b/mod/settings.php index 1b62499c22..3efdbf6bde 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -1,7 +1,7 @@ <?php -if(! function_exists('get_theme_config_file')) { -function get_theme_config_file($theme) { + +function get_theme_config_file($theme){ $a = get_app(); $base_theme = $a->theme_info['extends']; @@ -13,9 +13,7 @@ function get_theme_config_file($theme) { } return null; } -} -if(! function_exists('settings_init')) { function settings_init(&$a) { if(! local_user()) { @@ -112,10 +110,10 @@ function settings_init(&$a) { '$class' => 'settings-widget', '$items' => $tabs, )); -} + } -if(! function_exists('settings_post')) { + function settings_post(&$a) { if(! local_user()) @@ -632,9 +630,8 @@ function settings_post(&$a) { goaway($a->get_baseurl(true) . '/settings' ); return; // NOTREACHED } -} -if(! function_exists('settings_content')) { + function settings_content(&$a) { $o = ''; @@ -1298,5 +1295,6 @@ function settings_content(&$a) { $o .= '</form>' . "\r\n"; return $o; + } -} + diff --git a/mod/share.php b/mod/share.php index f3a221eb8e..085da4e30d 100644 --- a/mod/share.php +++ b/mod/share.php @@ -1,14 +1,12 @@ <?php - -if(! function_exists('share_init')) { function share_init(&$a) { $post_id = (($a->argc > 1) ? intval($a->argv[1]) : 0); if((! $post_id) || (! local_user())) killme(); - $r = q("SELECT item.*, contact.network FROM `item` - inner join contact on `item`.`contact-id` = `contact`.`id` + $r = q("SELECT item.*, contact.network FROM `item` + inner join contact on `item`.`contact-id` = `contact`.`id` WHERE `item`.`id` = %d AND `item`.`uid` = %d LIMIT 1", intval($post_id), @@ -42,9 +40,7 @@ function share_init(&$a) { echo $o; killme(); } -} -if(! function_exists('share_header')) { function share_header($author, $profile, $avatar, $guid, $posted, $link) { $header = "[share author='".str_replace(array("'", "[", "]"), array("'", "[", "]"),$author). "' profile='".str_replace(array("'", "[", "]"), array("'", "[", "]"),$profile). @@ -60,4 +56,3 @@ function share_header($author, $profile, $avatar, $guid, $posted, $link) { return $header; } -} diff --git a/mod/smilies.php b/mod/smilies.php index 4d498b6746..c47f95da76 100644 --- a/mod/smilies.php +++ b/mod/smilies.php @@ -1,7 +1,3 @@ <?php -if(! function_exists('smilies_content')) { -function smilies_content(&$a) { - return smilies('',true); -} -} +function smilies_content(&$a) { return smilies('',true); } diff --git a/mod/starred.php b/mod/starred.php index b4cc326787..2a89ac768b 100644 --- a/mod/starred.php +++ b/mod/starred.php @@ -1,6 +1,6 @@ <?php -if(! function_exists('starred_init')) { + function starred_init(&$a) { require_once("include/threads.php"); @@ -47,4 +47,3 @@ function starred_init(&$a) { echo json_encode($starred); killme(); } -} diff --git a/mod/statistics_json.php b/mod/statistics_json.php index 98cc708d26..21a9a0521c 100644 --- a/mod/statistics_json.php +++ b/mod/statistics_json.php @@ -5,7 +5,6 @@ require_once("include/plugin.php"); -if(! function_exists('statistics_json_init')) { function statistics_json_init(&$a) { if (!get_config("system", "nodeinfo")) { @@ -58,4 +57,3 @@ function statistics_json_init(&$a) { logger("statistics_init: printed ".print_r($statistics, true), LOGGER_DATA); killme(); } -} diff --git a/mod/subthread.php b/mod/subthread.php index 6cbaa1d2a7..1486a33b42 100644 --- a/mod/subthread.php +++ b/mod/subthread.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); -if(! function_exists('subthread_content')) { + function subthread_content(&$a) { if(! local_user() && ! remote_user()) { @@ -47,7 +47,7 @@ function subthread_content(&$a) { $remote_owner = $r[0]; } - // this represents the post owner on this system. + // this represents the post owner on this system. $r = q("SELECT `contact`.*, `user`.`nickname` FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid` WHERE `contact`.`self` = 1 AND `contact`.`uid` = %d LIMIT 1", @@ -103,7 +103,7 @@ EOT; $bodyverb = t('%1$s is following %2$s\'s %3$s'); if(! isset($bodyverb)) - return; + return; $arr = array(); @@ -123,7 +123,7 @@ EOT; $arr['author-name'] = $contact['name']; $arr['author-link'] = $contact['url']; $arr['author-avatar'] = $contact['thumb']; - + $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; $plink = '[url=' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . ']' . $post_type . '[/url]'; @@ -154,5 +154,7 @@ EOT; call_hooks('post_local_end', $arr); killme(); + } -} + + diff --git a/mod/suggest.php b/mod/suggest.php index 8f5f4f6a12..b73c2cd1b6 100644 --- a/mod/suggest.php +++ b/mod/suggest.php @@ -3,7 +3,7 @@ require_once('include/socgraph.php'); require_once('include/contact_widgets.php'); -if(! function_exists('suggest_init')) { + function suggest_init(&$a) { if(! local_user()) return; @@ -42,13 +42,13 @@ function suggest_init(&$a) { ); } } -} + } -if(! function_exists('suggest_content')) { + function suggest_content(&$a) { require_once("mod/proxy.php"); @@ -110,9 +110,8 @@ function suggest_content(&$a) { $o .= replace_macros($tpl,array( '$title' => t('Friend Suggestions'), '$contacts' => $entries, - + )); return $o; } -} diff --git a/mod/tagger.php b/mod/tagger.php index bee37015ea..2c469a58bb 100644 --- a/mod/tagger.php +++ b/mod/tagger.php @@ -4,7 +4,7 @@ require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); -if(! function_exists('tagger_content')) { + function tagger_content(&$a) { if(! local_user() && ! remote_user()) { @@ -95,7 +95,7 @@ EOT; $bodyverb = t('%1$s tagged %2$s\'s %3$s with %4$s'); if(! isset($bodyverb)) - return; + return; $termlink = html_entity_decode('⌗') . '[url=' . $a->get_baseurl() . '/search?tag=' . urlencode($term) . ']'. $term . '[/url]'; @@ -115,7 +115,7 @@ EOT; $arr['author-name'] = $contact['name']; $arr['author-link'] = $contact['url']; $arr['author-avatar'] = $contact['thumb']; - + $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; $plink = '[url=' . $item['plink'] . ']' . $post_type . '[/url]'; @@ -216,5 +216,5 @@ EOT; return; // NOTREACHED -} + } diff --git a/mod/tagrm.php b/mod/tagrm.php index 70b3ef048f..176986bc38 100644 --- a/mod/tagrm.php +++ b/mod/tagrm.php @@ -2,7 +2,6 @@ require_once('include/bbcode.php'); -if(! function_exists('tagrm_post')) { function tagrm_post(&$a) { if(! local_user()) @@ -41,13 +40,13 @@ function tagrm_post(&$a) { info( t('Tag removed') . EOL ); goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); - + // NOTREACHED -} + } -if(! function_exists('tagrm_content')) { + function tagrm_content(&$a) { $o = ''; @@ -96,5 +95,5 @@ function tagrm_content(&$a) { $o .= '</form>'; return $o; -} + } diff --git a/mod/toggle_mobile.php b/mod/toggle_mobile.php index dbf0996bba..00991e44ca 100644 --- a/mod/toggle_mobile.php +++ b/mod/toggle_mobile.php @@ -1,6 +1,5 @@ <?php -if(! function_exists('toggle_mobile_init')) { function toggle_mobile_init(&$a) { if(isset($_GET['off'])) @@ -15,4 +14,4 @@ function toggle_mobile_init(&$a) { goaway($address); } -} + diff --git a/mod/uexport.php b/mod/uexport.php index eacf300f3f..a44620a976 100644 --- a/mod/uexport.php +++ b/mod/uexport.php @@ -1,7 +1,6 @@ <?php -if(! function_exists('uexport_init')) { -function uexport_init(&$a) { +function uexport_init(&$a){ if(! local_user()) killme(); @@ -56,10 +55,8 @@ function uexport_init(&$a) { )); */ } -} -if(! function_exists('uexport_content')) { -function uexport_content(&$a) { +function uexport_content(&$a){ if ($a->argc > 1) { header("Content-type: application/json"); @@ -89,10 +86,9 @@ function uexport_content(&$a) { '$options' => $options )); -} + } -if(! function_exists('_uexport_multirow')) { function _uexport_multirow($query) { $result = array(); $r = q($query); @@ -107,9 +103,7 @@ function _uexport_multirow($query) { } return $result; } -} -if(! function_exists('_uexport_row')) { function _uexport_row($query) { $result = array(); $r = q($query); @@ -121,10 +115,9 @@ function _uexport_row($query) { } return $result; } -} -if(! function_exists('uexport_account')) { -function uexport_account($a) { + +function uexport_account($a){ $user = _uexport_row( sprintf( "SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval(local_user()) ) @@ -160,9 +153,9 @@ function uexport_account($a) { 'version' => FRIENDICA_VERSION, 'schema' => DB_UPDATE_VERSION, 'baseurl' => $a->get_baseurl(), - 'user' => $user, - 'contact' => $contact, - 'profile' => $profile, + 'user' => $user, + 'contact' => $contact, + 'profile' => $profile, 'photo' => $photo, 'pconfig' => $pconfig, 'group' => $group, @@ -171,15 +164,14 @@ function uexport_account($a) { //echo "<pre>"; var_dump(json_encode($output)); killme(); echo json_encode($output); -} + } /** * echoes account data and items as separated json, one per line */ -if(! function_exists('uexport_all')) { function uexport_all(&$a) { - + uexport_account($a); echo "\n"; @@ -207,5 +199,5 @@ function uexport_all(&$a) { $output = array('item' => $r); echo json_encode($output)."\n"; } -} + } diff --git a/mod/uimport.php b/mod/uimport.php index 942268b0ef..7ed5648d9e 100644 --- a/mod/uimport.php +++ b/mod/uimport.php @@ -5,7 +5,6 @@ require_once("include/uimport.php"); -if(! function_exists('uimport_post')) { function uimport_post(&$a) { switch($a->config['register_policy']) { case REGISTER_OPEN: @@ -28,18 +27,16 @@ function uimport_post(&$a) { $verified = 0; break; } - + if (x($_FILES,'accountfile')){ /// @TODO Pass $blocked / $verified, send email to admin on REGISTER_APPROVE import_account($a, $_FILES['accountfile']); return; } } -} -if(! function_exists('uimport_content')) { function uimport_content(&$a) { - + if((! local_user()) && ($a->config['register_policy'] == REGISTER_CLOSED)) { notice("Permission denied." . EOL); return; @@ -54,8 +51,8 @@ function uimport_content(&$a) { return; } } - - + + if(x($_SESSION,'theme')) unset($_SESSION['theme']); if(x($_SESSION,'mobile-theme')) @@ -74,4 +71,3 @@ function uimport_content(&$a) { ), )); } -} diff --git a/mod/update_community.php b/mod/update_community.php index 396f4234c0..512629b005 100644 --- a/mod/update_community.php +++ b/mod/update_community.php @@ -4,7 +4,6 @@ require_once('mod/community.php'); -if(! function_exists('update_community_content')) { function update_community_content(&$a) { header("Content-type: text/html"); @@ -30,5 +29,5 @@ function update_community_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); -} -} + +} \ No newline at end of file diff --git a/mod/update_display.php b/mod/update_display.php index 9400cb39a6..25b0f77926 100644 --- a/mod/update_display.php +++ b/mod/update_display.php @@ -5,7 +5,6 @@ require_once('mod/display.php'); require_once('include/group.php'); -if(! function_exists('update_display_content')) { function update_display_content(&$a) { $profile_uid = intval($_GET['p']); @@ -35,5 +34,5 @@ function update_display_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); -} + } diff --git a/mod/update_network.php b/mod/update_network.php index b2e7abc90c..1bf3746575 100644 --- a/mod/update_network.php +++ b/mod/update_network.php @@ -5,7 +5,6 @@ require_once('mod/network.php'); require_once('include/group.php'); -if(! function_exists('update_network_content')) { function update_network_content(&$a) { $profile_uid = intval($_GET['p']); @@ -38,5 +37,5 @@ function update_network_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); -} + } diff --git a/mod/update_notes.php b/mod/update_notes.php index e1e4f1d795..6b8fff5115 100644 --- a/mod/update_notes.php +++ b/mod/update_notes.php @@ -9,7 +9,6 @@ require_once('mod/notes.php'); -if(! function_exists('update_notes_content')) { function update_notes_content(&$a) { $profile_uid = intval($_GET['p']); @@ -21,8 +20,8 @@ function update_notes_content(&$a) { /** * - * Grab the page inner contents by calling the content function from the profile module directly, - * but move any image src attributes to another attribute name. This is because + * Grab the page inner contents by calling the content function from the profile module directly, + * but move any image src attributes to another attribute name. This is because * some browsers will prefetch all the images for the page even if we don't need them. * The only ones we need to fetch are those for new page additions, which we'll discover * on the client side and then swap the image back. @@ -53,5 +52,5 @@ function update_notes_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); -} -} + +} \ No newline at end of file diff --git a/mod/update_profile.php b/mod/update_profile.php index 93a94ae0d8..2492a48ee4 100644 --- a/mod/update_profile.php +++ b/mod/update_profile.php @@ -9,7 +9,6 @@ require_once('mod/profile.php'); -if(! function_exists('update_profile_content')) { function update_profile_content(&$a) { $profile_uid = intval($_GET['p']); @@ -25,8 +24,8 @@ function update_profile_content(&$a) { /** * - * Grab the page inner contents by calling the content function from the profile module directly, - * but move any image src attributes to another attribute name. This is because + * Grab the page inner contents by calling the content function from the profile module directly, + * but move any image src attributes to another attribute name. This is because * some browsers will prefetch all the images for the page even if we don't need them. * The only ones we need to fetch are those for new page additions, which we'll discover * on the client side and then swap the image back. @@ -57,5 +56,5 @@ function update_profile_content(&$a) { echo "</section>"; echo "</body></html>\r\n"; killme(); -} -} + +} \ No newline at end of file diff --git a/mod/videos.php b/mod/videos.php index f9db7b05b1..bf8d696b60 100644 --- a/mod/videos.php +++ b/mod/videos.php @@ -5,7 +5,7 @@ require_once('include/bbcode.php'); require_once('include/security.php'); require_once('include/redir.php'); -if(! function_exists('videos_init')) { + function videos_init(&$a) { if($a->argc > 1) @@ -102,9 +102,9 @@ function videos_init(&$a) { return; } -} -if(! function_exists('videos_post')) { + + function videos_post(&$a) { $owner_uid = $a->data['user']['uid']; @@ -176,11 +176,11 @@ function videos_post(&$a) { } goaway($a->get_baseurl() . '/videos/' . $a->data['user']['nickname']); -} + } -if(! function_exists('videos_content')) { + function videos_content(&$a) { // URLs (most aren't currently implemented): @@ -407,4 +407,4 @@ function videos_content(&$a) { $o .= paginate($a); return $o; } -} + diff --git a/mod/view.php b/mod/view.php index a270baeaa1..15b3733b3f 100644 --- a/mod/view.php +++ b/mod/view.php @@ -2,18 +2,16 @@ /** * load view/theme/$current_theme/style.php with friendica contex */ - -if(! function_exists('view_init')) { -function view_init($a) { + +function view_init($a){ header("Content-Type: text/css"); - + if ($a->argc == 4){ $theme = $a->argv[2]; $THEMEPATH = "view/theme/$theme"; if(file_exists("view/theme/$theme/style.php")) require_once("view/theme/$theme/style.php"); } - + killme(); } -} diff --git a/mod/viewcontacts.php b/mod/viewcontacts.php index acb51f0cb4..04520e0d93 100644 --- a/mod/viewcontacts.php +++ b/mod/viewcontacts.php @@ -2,7 +2,6 @@ require_once('include/Contact.php'); require_once('include/contact_selectors.php'); -if(! function_exists('viewcontacts_init')) { function viewcontacts_init(&$a) { if((get_config('system','block_public')) && (! local_user()) && (! remote_user())) { @@ -27,9 +26,8 @@ function viewcontacts_init(&$a) { profile_load($a,$a->argv[1]); } } -} -if(! function_exists('viewcontacts_content')) { + function viewcontacts_content(&$a) { require_once("mod/proxy.php"); @@ -123,4 +121,3 @@ function viewcontacts_content(&$a) { return $o; } -} diff --git a/mod/viewsrc.php b/mod/viewsrc.php index 1203d18fc9..3fa4eaed53 100644 --- a/mod/viewsrc.php +++ b/mod/viewsrc.php @@ -1,6 +1,6 @@ <?php -if(! function_exists('viewsrc_content')) { + function viewsrc_content(&$a) { if(! local_user()) { @@ -16,7 +16,7 @@ function viewsrc_content(&$a) { return; } - $r = q("SELECT `item`.`body` FROM `item` + $r = q("SELECT `item`.`body` FROM `item` WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 AND `item`.`id` = '%s' LIMIT 1", @@ -33,4 +33,4 @@ function viewsrc_content(&$a) { } return $o; } -} + diff --git a/mod/wall_attach.php b/mod/wall_attach.php index 20e646cb9a..68752a0e1f 100644 --- a/mod/wall_attach.php +++ b/mod/wall_attach.php @@ -3,7 +3,6 @@ require_once('include/attach.php'); require_once('include/datetime.php'); -if(! function_exists('wall_attach_post')) { function wall_attach_post(&$a) { $r_json = (x($_GET,'response') && $_GET['response']=='json'); @@ -191,4 +190,3 @@ function wall_attach_post(&$a) { killme(); // NOTREACHED } -} diff --git a/mod/wall_upload.php b/mod/wall_upload.php index 2851807d57..b815348c70 100644 --- a/mod/wall_upload.php +++ b/mod/wall_upload.php @@ -2,7 +2,6 @@ require_once('include/Photo.php'); -if(! function_exists('wall_upload_post')) { function wall_upload_post(&$a, $desktopmode = true) { logger("wall upload: starting new upload", LOGGER_DEBUG); @@ -298,4 +297,3 @@ function wall_upload_post(&$a, $desktopmode = true) { killme(); // NOTREACHED } -} diff --git a/mod/wallmessage.php b/mod/wallmessage.php index a01dfd2b9f..b8859badd3 100644 --- a/mod/wallmessage.php +++ b/mod/wallmessage.php @@ -2,7 +2,6 @@ require_once('include/message.php'); -if(! function_exists('wallmessage_post')) { function wallmessage_post(&$a) { $replyto = get_my_url(); @@ -49,7 +48,7 @@ function wallmessage_post(&$a) { $body = str_replace("\r\n","\n",$body); $body = str_replace("\n\n","\n",$body); - + $ret = send_wallmessage($user, $body, $subject, $replyto); switch($ret){ @@ -70,10 +69,10 @@ function wallmessage_post(&$a) { } // goaway($a->get_baseurl() . '/profile/' . $user['nickname']); -} + } -if(! function_exists('wallmessage_content')) { + function wallmessage_content(&$a) { if(! get_my_url()) { @@ -135,9 +134,9 @@ function wallmessage_content(&$a) { '$nickname' => $user['nickname'], '$linkurl' => t('Please enter a link URL:') )); + - - + $tpl = get_markup_template('wallmessage.tpl'); $o .= replace_macros($tpl,array( '$header' => t('Send Private Message'), @@ -159,4 +158,3 @@ function wallmessage_content(&$a) { return $o; } -} diff --git a/mod/webfinger.php b/mod/webfinger.php index 4024671b02..74bd2c9543 100644 --- a/mod/webfinger.php +++ b/mod/webfinger.php @@ -1,13 +1,14 @@ <?php -if(! function_exists('webfinger_content')) { + + function webfinger_content(&$a) { $o .= '<h3>Webfinger Diagnostic</h3>'; $o .= '<form action="webfinger" method="get">'; $o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] .'" />'; - $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; @@ -23,4 +24,3 @@ function webfinger_content(&$a) { } return $o; } -} diff --git a/mod/xrd.php b/mod/xrd.php index f8e0a9c409..c23119145c 100644 --- a/mod/xrd.php +++ b/mod/xrd.php @@ -2,7 +2,6 @@ require_once('include/crypto.php'); -if(! function_exists('xrd_init')) { function xrd_init(&$a) { $uri = urldecode(notags(trim($_GET['uri']))); @@ -78,5 +77,5 @@ function xrd_init(&$a) { echo $arr['xml']; killme(); -} + } From 991cbd604a588e676bf316aa120f223071bdff94 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Mon, 8 Feb 2016 01:56:15 +0100 Subject: [PATCH 058/273] contact-edit-actions-button: initial commit --- mod/contacts.php | 71 ++++++++++++++++++++++++++++++++- view/global.css | 69 ++++++++++++++++++++++---------- view/templates/contact_edit.tpl | 24 ++++++----- 3 files changed, 132 insertions(+), 32 deletions(-) diff --git a/mod/contacts.php b/mod/contacts.php index 0b421433e0..248ed47ab3 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -565,6 +565,8 @@ function contacts_content(&$a) { ($contact['rel'] == CONTACT_IS_FOLLOWER)) $follow = $a->get_baseurl(true)."/follow?url=".urlencode($contact["url"]); + $contact_actions = contact_action_menu($contact); + $o .= replace_macros($tpl, array( //'$header' => t('Contact Editor'), @@ -574,7 +576,7 @@ function contacts_content(&$a) { '$lbl_vis2' => sprintf( t('Please choose the profile you would like to display to %s when viewing your profile securely.'), $contact['name']), '$lbl_info1' => t('Contact Information / Notes'), '$infedit' => t('Edit contact notes'), - '$common_text' => $common_text, + //'$common_text' => $common_text, '$common_link' => $a->get_baseurl(true) . '/common/loc/' . local_user() . '/' . $contact['id'], '$all_friends' => $all_friends, '$relation_text' => $relation_text, @@ -622,7 +624,9 @@ function contacts_content(&$a) { '$about' => bbcode($contact["about"], false, false), '$about_label' => t("About:"), '$keywords' => $contact["keywords"], - '$keywords_label' => t("Tags:") + '$keywords_label' => t("Tags:"), + '$contact_action_button' => t("Actions"), + '$contact_actions' => $contact_actions, )); @@ -954,3 +958,66 @@ function _contact_detail_for_template($rr){ ); } + +function contact_action_menu($contact) { + + $contact_action_menu = array( + 'suggest' => array( + 'label' => t('Suggest friends'), + 'url' => app::get_baseurl(true) . '/fsuggest/' . $contact['id'], + 'title' => '', + 'sel' => '', + 'id' => 'suggest', + ), + + 'update' => array( + 'label' => t('Update now'), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/update', + 'title' => '', + 'sel' => '', + 'id' => 'update', + ), + + 'repair' => array( + 'label' => t('Repair'), + 'url' => app::get_baseurl(true) . '/crepair/' . $contact['id'], + 'title' => t('Advanced Contact Settings'), + 'sel' => '', + 'id' => 'repair', + ), + + 'block' => array( + 'label' => (intval($contact['blocked']) ? t('Unblock') : t('Block') ), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/block', + 'title' => t('Toggle Blocked status'), + 'sel' => (intval($contact['blocked']) ? 'active' : ''), + 'id' => 'toggle-block', + ), + + 'ignore' => array( + 'label' => (intval($contact['readonly']) ? t('Unignore') : t('Ignore') ), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/ignore', + 'title' => t('Toggle Ignored status'), + 'sel' => (intval($contact['readonly']) ? 'active' : ''), + 'id' => 'toggle-ignore', + ), + + 'archive' => array( + 'label' => (intval($contact['archive']) ? t('Unarchive') : t('Archive') ), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/archive', + 'title' => t('Toggle Archive status'), + 'sel' => (intval($contact['archive']) ? 'active' : ''), + 'id' => 'toggle-archive', + ), + + 'delete' => array( + 'label' => t('Delete'), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/drop', + 'title' => t('Delete contact'), + 'sel' => '', + 'id' => 'delete', + ) + ); + + return $contact_action_menu; +} diff --git a/view/global.css b/view/global.css index 8646bf8e44..df001ed362 100644 --- a/view/global.css +++ b/view/global.css @@ -1,6 +1,28 @@ /* General style rules .*/ .pull-right { float: right } +/* General designing elements */ +.btn { + outline: none; + -moz-box-shadow: inset 0px 1px 0px 0px #ffffff; + -webkit-box-shadow: inset 0px 1px 0px 0px #ffffff; + box-shadow: inset 0px 1px 0px 0px #ffffff; + background-color: #ededed; + text-indent: 0; + border: 1px solid #dcdcdc; + display: inline-block; + color: #777777; + padding: 5px 10px; + text-align: center; +} + +ul.menu-popup li.divider { + height: 1px; + margin: 3px 0; + overflow: hidden; + background-color: #2d2d2d;; +} + /* List of social Networks */ img.connector, img.connector-disabled { height: 40px; @@ -277,20 +299,20 @@ a { margin: 10px 0 10px; } .version-match { - font-weight: bold; - color: #00a700; + font-weight: bold; + color: #00a700; } .federation-graph { - width: 400px; - height: 400px; - float: right; - margin: 20px; + width: 400px; + height: 400px; + float: right; + margin: 20px; } .federation-network-graph { - width: 240px; - height: 240px; - float: left; - margin: 20px; + width: 240px; + height: 240px; + float: left; + margin: 20px; } ul.federation-stats, ul.credits { @@ -302,10 +324,10 @@ ul.credits li { width: 240px; } table#federation-stats { - width: 100%; + width: 100%; } td.federation-data { - border-bottom: 1px solid #000; + border-bottom: 1px solid #000; } .contact-entry-photo img { @@ -329,25 +351,30 @@ td.federation-data { } .crepair-label { - margin-top: 10px; - float: left; - width: 250px; + margin-top: 10px; + float: left; + width: 250px; } .crepair-input { - margin-top: 10px; - float: left; - width: 200px; + margin-top: 10px; + float: left; + width: 200px; } .renderinfo { - clear: both; + clear: both; } .p-addr { - clear: both; + clear: both; } #live-community { - clear: both; + clear: both; } + +/* contact-edit */ +#contact-edit-actions { + float: right; +} \ No newline at end of file diff --git a/view/templates/contact_edit.tpl b/view/templates/contact_edit.tpl index 15863b6a27..682ebfa36a 100644 --- a/view/templates/contact_edit.tpl +++ b/view/templates/contact_edit.tpl @@ -4,6 +4,21 @@ {{$tab_str}} + <div id="contact-edit-actions"> + <a class="btn" rel="#contact-actions-menu" href="#" id="contact-edit-actions-button">{{$contact_action_button}}</a> + + <ul role="menu" aria-haspopup="true" id="contact-actions-menu" class="menu-popup" > + {{if $lblsuggest}}<li><a href="#" title="{{$contact_actions.suggest.title}}" onclick="window.location.href='{{$contact_actions.suggest.url}}'; return false;">{{$contact_actions.suggest.label}}</a></li>{{/if}} + {{if $poll_enabled}}<li><a href="#" title="{{$contact_actions.update.title}}" onclick="window.location.href='{{$contact_actions.update.url}}'; return false;">{{$contact_actions.update.label}}</a></li>{{/if}} + <li><a href="#" title="{{$contact_actions.repair.title}}" onclick="window.location.href='{{$contact_actions.repair.url}}'; return false;">{{$contact_actions.repair.label}}</a></li> + <li class="divider"></li> + <li><a href="#" title="{{$contact_actions.block.title}}" onclick="window.location.href='{{$contact_actions.block.url}}'; return false;">{{$contact_actions.block.label}}</a></li> + <li><a href="#" title="{{$contact_actions.ignore.title}}" onclick="window.location.href='{{$contact_actions.ignore.url}}'; return false;">{{$contact_actions.ignore.label}}</a></li> + <li><a href="#" title="{{$contact_actions.archive.title}}" onclick="window.location.href='{{$contact_actions.archive.url}}'; return false;">{{$contact_actions.archive.label}}</a></li> + <li><a href="#" title="{{$contact_actions.delete.title}}" onclick="return confirmDelete();">{{$contact_actions.delete.label}}</a></li> + </ul> + </div> + <div id="contact-edit-drop-link" > <a href="contacts/{{$contact_id}}/drop" class="icon drophide" id="contact-edit-drop-link" onclick="return confirmDelete();" title="{{$delete}}" onmouseover="imgbright(this);" onmouseout="imgdull(this);"></a> </div> @@ -35,15 +50,6 @@ </ul> <ul> - - {{if $common_text}} - <li><div id="contact-edit-common"><a href="{{$common_link}}">{{$common_text}}</a></div></li> - {{/if}} - {{if $all_friends}} - <li><div id="contact-edit-allfriends"><a href="allfriends/{{$contact_id}}">{{$all_friends}}</a></div></li> - {{/if}} - - <!-- <li><a href="network/0?nets=all&cid={{$contact_id}}" id="contact-edit-view-recent">{{$lblrecent}}</a></li> --> {{if $lblsuggest}} <li><a href="fsuggest/{{$contact_id}}" id="contact-edit-suggest">{{$lblsuggest}}</a></li> From e267630c546190b2a2cf437d98bbd415d48c4de2 Mon Sep 17 00:00:00 2001 From: rabuzarus <trebor@central-unit> Date: Mon, 8 Feb 2016 01:59:05 +0100 Subject: [PATCH 059/273] datetime.php: cleanup - delete some dots which shouldn't be there --- include/datetime.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/datetime.php b/include/datetime.php index 6983b6431d..89305a2406 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -75,8 +75,8 @@ function select_timezone($current = 'America/Los_Angeles') { * * Return a select using 'field_select_raw' template, with timezones * groupped (primarily) by continent -.* arguments follow convetion as other field_* template array: -.* 'name', 'label', $value, 'help' + * arguments follow convetion as other field_* template array: + * 'name', 'label', $value, 'help' * * @param string $name Name of the selector * @param string $label Label for the selector From 756b90a4e073c5f1e7cfb0314262b90f408b8f83 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Mon, 8 Feb 2016 09:47:59 +0100 Subject: [PATCH 060/273] add docs, rewrite part of the notification api list notifications and set note as seen functionalities are now splitted in two functions, with correct http method requirement. Fixed returned value from `notification/seen` --- include/NotificationsManager.php | 30 +++++++++++-- include/api.php | 74 +++++++++++++++++++++----------- 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/include/NotificationsManager.php b/include/NotificationsManager.php index 8b0ca9e13d..b7e31d4f65 100644 --- a/include/NotificationsManager.php +++ b/include/NotificationsManager.php @@ -1,6 +1,13 @@ <?php +/** + * @file include/NotificationsManager.php + */ require_once("include/datetime.php"); +require_once("include/bbcode.php"); +/** + * @brief Read and write notifications from/to database + */ class NotificationsManager { private $a; @@ -8,12 +15,27 @@ class NotificationsManager { $this->a = get_app(); } + /** + * @brief set some extra note properties + * + * @param array $notes array of note arrays from db + * @return array Copy of input array with added properties + * + * Set some extra properties to note array from db: + * - timestamp as int in default TZ + * - date_rel : relative date string + * - msg_html: message as html string + * - msg_plain: message as plain text string + */ private function _set_extra($notes) { $rets = array(); foreach($notes as $n) { $local_time = datetime_convert('UTC',date_default_timezone_get(),$n['date']); $n['timestamp'] = strtotime($local_time); $n['date_rel'] = relative_date($n['date']); + $n['msg_html'] = bbcode($n['msg'], false, false, false, false); + $n['msg_plain'] = explode("\n",trim(html2plain($n['msg_html'], 0)))[0]; + $rets[] = $n; } return $rets; @@ -57,7 +79,7 @@ class NotificationsManager { if ($limit!="") $limit = " LIMIT ".$limit; - $r = q("SELECT * from notify where uid = %d $filter_sql order by $order_sql $limit", + $r = q("SELECT * FROM notify WHERE uid = %d $filter_sql ORDER BY $order_sql $limit", intval(local_user()) ); if ($r!==false && count($r)>0) return $this->_set_extra($r); @@ -71,7 +93,7 @@ class NotificationsManager { * @return array note values or null if not found */ public function getByID($id) { - $r = q("select * from notify where id = %d and uid = %d limit 1", + $r = q("SELECT * FROM notify WHERE id = %d AND uid = %d LIMIT 1", intval($id), intval(local_user()) ); @@ -89,7 +111,7 @@ class NotificationsManager { * @return bool true on success, false on errors */ public function setSeen($note, $seen = true) { - return q("update notify set seen = %d where ( link = '%s' or ( parent != 0 and parent = %d and otype = '%s' )) and uid = %d", + return q("UPDATE notify SET seen = %d WHERE ( link = '%s' OR ( parent != 0 AND parent = %d AND otype = '%s' )) AND uid = %d", intval($seen), dbesc($note['link']), intval($note['parent']), @@ -105,7 +127,7 @@ class NotificationsManager { * @return bool true on success, false on error */ public function setAllSeen($seen = true) { - return q("update notify set seen = %d where uid = %d", + return q("UPDATE notify SET seen = %d WHERE uid = %d", intval($seen), intval(local_user()) ); diff --git a/include/api.php b/include/api.php index 5acc816575..16481201e3 100644 --- a/include/api.php +++ b/include/api.php @@ -690,6 +690,11 @@ function api_array_to_xml($data, $ename="") { $attrs=""; $childs=""; + if (count($data)==1 && !is_array($data[0])) { + $ename = array_keys($data)[0]; + $v = $data[$ename]; + return "<$ename>$v</$ename>"; + } foreach($data as $k=>$v) { $k=trim($k,'$'); if (!is_array($v)) { @@ -3415,41 +3420,62 @@ api_register_func('api/friendica/activity/unattendmaybe', 'api_friendica_activity', true, API_METHOD_POST); /** - * returns notifications - * if called with note id set note seen and returns associated item (if possible) - */ + * @brief Returns notifications + * + * @param App $a + * @param string $type Known types are 'atom', 'rss', 'xml' and 'json' + * @return string + */ function api_friendica_notification(&$a, $type) { if (api_user()===false) throw new ForbiddenException(); - + if ($a->argc!==3) throw new BadRequestException("Invalid argument count"); $nm = new NotificationsManager(); - if ($a->argc==3) { - $notes = $nm->getAll(array(), "+seen -date", 50); - return api_apply_template("<auto>", $type, array('$notes' => $notes)); - } - if ($a->argc==4) { - $note = $nm->getByID(intval($a->argv[3])); - if (is_null($note)) throw new BadRequestException("Invalid argument"); - $nm->setSeen($note); - if ($note['otype']=='item') { - // would be really better with a ItemsManager and $im->getByID() :-P - $r = q("SELECT * FROM item WHERE id=%d AND uid=%d", - intval($note['iid']), - intval(local_user()) - ); - if ($r===false) throw new NotFoundException(); + $notes = $nm->getAll(array(), "+seen -date", 50); + return api_apply_template("<auto>", $type, array('$notes' => $notes)); + } + + /** + * @brief Set notification as seen and returns associated item (if possible) + * + * POST request with 'id' param as notification id + * + * @param App $a + * @param string $type Known types are 'atom', 'rss', 'xml' and 'json' + * @return string + */ + function api_friendica_notification_seen(&$a, $type){ + if (api_user()===false) throw new ForbiddenException(); + if ($a->argc!==4) throw new BadRequestException("Invalid argument count"); + + $id = (x($_REQUEST, 'id') ? intval($_REQUEST['id']) : 0); + + $nm = new NotificationsManager(); + $note = $nm->getByID($id); + if (is_null($note)) throw new BadRequestException("Invalid argument"); + + $nm->setSeen($note); + if ($note['otype']=='item') { + // would be really better with an ItemsManager and $im->getByID() :-P + $r = q("SELECT * FROM item WHERE id=%d AND uid=%d", + intval($note['iid']), + intval(local_user()) + ); + if ($r!==false) { + // we found the item, return it to the user $user_info = api_get_user($a); $ret = api_format_items($r,$user_info); $data = array('$statuses' => $ret); return api_apply_template("timeline", $type, $data); - } else { - return api_apply_template('test', $type, array('ok' => $ok)); } - - } - throw new BadRequestException("Invalid argument count"); + // the item can't be found, but we set the note as seen, so we count this as a success + } + return api_apply_template('<auto>', $type, array('status' => "success")); } + + api_register_func('api/friendica/notification/seen', 'api_friendica_notification_seen', true, API_METHOD_POST); api_register_func('api/friendica/notification', 'api_friendica_notification', true, API_METHOD_GET); + /* To.Do: From 870d2f844d83741c7fc1878dda72807dab92d992 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Mon, 8 Feb 2016 10:22:12 +0100 Subject: [PATCH 061/273] Update API docs Add `friendica/notification` and `friendica/notification/seen` endpoints Fix some list rendering issues Move all `friendica/*` endpoints under "Implemented API calls (not compatible with other APIs)" section Add info about permitted HTTP methods and required auth --- doc/api.md | 450 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 263 insertions(+), 187 deletions(-) diff --git a/doc/api.md b/doc/api.md index ced078f556..f050ae4304 100644 --- a/doc/api.md +++ b/doc/api.md @@ -7,6 +7,21 @@ 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 @@ -37,36 +52,37 @@ Error body is json: ``` - { - "error": "Specific error message", - "request": "API path requested", - "code": "HTTP error code" - } +{ +"error": "Specific error message", +"request": "API path requested", +"code": "HTTP error code" +} ``` xml: ``` - <status> - <error>Specific error message</error> - <request>API path requested</request> - <code>HTTP error code</code> - </status> +<status> +<error>Specific error message</error> +<request>API path requested</request> +<code>HTTP error code</code> +</status> ``` --- -### account/rate_limit_status +### account/rate_limit_status (*; AUTH) --- -### account/verify_credentials +### 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 +### conversation/show (*; AUTH) Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id. -#### Parameters +#### Parameter * id: id of the post * count: Items per page (default: 20) * page: page number @@ -80,7 +96,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original * contributor_details --- -### direct_messages +### direct_messages (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -93,7 +109,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original * skip_status --- -### direct_messages/all +### direct_messages/all (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -102,7 +118,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original * getText: Defines the format of the status field. Can be "html" or "plain" --- -### direct_messages/conversation +### direct_messages/conversation (*; AUTH) Shows all direct messages of a conversation #### Parameters * count: Items per page (default: 20) @@ -113,7 +129,7 @@ Shows all direct messages of a conversation * uri: URI of the conversation --- -### direct_messages/new +### 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!) @@ -122,7 +138,7 @@ Shows all direct messages of a conversation * title: Title of the direct message --- -### direct_messages/sent +### direct_messages/sent (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -132,7 +148,7 @@ Shows all direct messages of a conversation * include_entities: "true" shows entities for pictures and links (Default: false) --- -### favorites +### favorites (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -144,22 +160,23 @@ Shows all direct messages of a conversation * user_id * screen_name -Favorites aren't displayed to other users, so "user_id" and "screen_name". So setting this value will result in an empty array. +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 +### favorites/create (POST,PUT; AUTH) #### Parameters * id * include_entities: "true" shows entities for pictures and links (Default: false) --- -### favorites/destroy +### favorites/destroy (POST,DELETE; AUTH) #### Parameters * id * include_entities: "true" shows entities for pictures and links (Default: false) --- -### followers/ids +### followers/ids (*; AUTH) #### Parameters * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false) @@ -171,139 +188,7 @@ Favorites aren't displayed to other users, so "user_id" and "screen_name". So se Friendica doesn't allow showing followers of other users. --- -### friendica/activity/<verb> -#### 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 -```<ok>true</ok>``` - -On error: -HTTP 400 BadRequest - ---- -### friendica/photo -#### 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": { - "<scale>": "url to image" - ... - }, - // if 'scale' is set - "datasize": "size in byte", - "data": "base64 encoded image data" - } -``` - -xml -``` - <photo> - <id>photo id</id> - <created>date(YYYY-MM-GG HH:MM:SS)</created> - <edited>date(YYYY-MM-GG HH:MM:SS)</edited> - <title>photo title</title> - <desc>photo description</desc> - <album>album name</album> - <filename>original file name</filename> - <type>mime type</type> - <height>number</height> - <width>number</width> - <profile>1 if is profile photo</profile> - <links type="array"> - <link type="mime type" scale="scale number" href="image url"/> - ... - </links> - </photo> -``` - ---- -### friendica/photos/list - -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 -``` - <photos type="array"> - <photo id="resource_id" - album="album name" - filename="original file name" - type="image mime type"> - "url to thumb sized image" - </photo> - ... - </photos> -``` - ---- -### friends/ids +### friends/ids (*; AUTH) #### Parameters * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false) @@ -315,15 +200,15 @@ xml Friendica doesn't allow showing friends of other users. --- -### help/test +### help/test (*) --- -### media/upload +### media/upload (POST,PUT; AUTH) #### Parameters * media: image data --- -### oauth/request_token +### oauth/request_token (*) #### Parameters * oauth_callback @@ -331,7 +216,7 @@ Friendica doesn't allow showing friends of other users. * x_auth_access_type --- -### oauth/access_token +### oauth/access_token (*) #### Parameters * oauth_verifier @@ -341,7 +226,7 @@ Friendica doesn't allow showing friends of other users. * x_auth_mode --- -### statuses/destroy +### statuses/destroy (POST,DELETE; AUTH) #### Parameters * id: message number * include_entities: "true" shows entities for pictures and links (Default: false) @@ -350,15 +235,21 @@ Friendica doesn't allow showing friends of other users. * trim_user --- -### statuses/followers +### statuses/followers (*; AUTH) + +#### Parameters + * include_entities: "true" shows entities for pictures and links (Default: false) --- -### statuses/friends +### statuses/friends (*; AUTH) + +#### Parameters + * include_entities: "true" shows entities for pictures and links (Default: false) --- -### statuses/friends_timeline +### statuses/friends_timeline (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -374,7 +265,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/home_timeline +### statuses/home_timeline (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -390,7 +281,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/mentions +### statuses/mentions (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -404,7 +295,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/public_timeline +### statuses/public_timeline (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -418,7 +309,7 @@ Friendica doesn't allow showing friends of other users. * trim_user --- -### statuses/replies +### statuses/replies (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -432,7 +323,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/retweet +### statuses/retweet (POST,PUT; AUTH) #### Parameters * id: message number * include_entities: "true" shows entities for pictures and links (Default: false) @@ -441,7 +332,7 @@ Friendica doesn't allow showing friends of other users. * trim_user --- -### statuses/show +### statuses/show (*; AUTH) #### Parameters * id: message number * conversation: if set to "1" show all messages of the conversation with the given id @@ -476,7 +367,7 @@ Friendica doesn't allow showing friends of other users. * display_coordinates --- -### statuses/user_timeline +### statuses/user_timeline (*; AUTH) #### Parameters * user_id: id of the user * screen_name: screen name (for technical reasons, this value is not unique!) @@ -489,15 +380,16 @@ Friendica doesn't allow showing friends of other users. * include_entities: "true" shows entities for pictures and links (Default: false) #### Unsupported parameters + * include_rts * trim_user * contributor_details --- -### statusnet/config +### statusnet/config (*) --- -### statusnet/version +### statusnet/version (*) #### Unsupported parameters * user_id @@ -507,7 +399,7 @@ Friendica doesn't allow showing friends of other users. Friendica doesn't allow showing followers of other users. --- -### users/search +### users/search (*) #### Parameters * q: name of the user @@ -517,7 +409,7 @@ Friendica doesn't allow showing followers of other users. * include_entities --- -### users/show +### users/show (*) #### Parameters * user_id: id of the user * screen_name: screen name (for technical reasons, this value is not unique!) @@ -533,8 +425,39 @@ Friendica doesn't allow showing friends of other users. ## Implemented API calls (not compatible with other APIs) + --- -### friendica/group_show +### friendica/activity/<verb> +#### 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 +```<ok>true</ok>``` + +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 @@ -542,22 +465,23 @@ Return all or a specified group of the user with the containing contacts as arra #### 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 +### 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 +#### 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 @@ -566,19 +490,22 @@ Array of: --- -### friendica/group_create +### 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“: +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 @@ -587,26 +514,175 @@ Array of: --- -### friendica/group_update +### 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": { +"<scale>": "url to image" +... +}, +// if 'scale' is set +"datasize": "size in byte", +"data": "base64 encoded image data" +} +``` + +xml +``` +<photo> +<id>photo id</id> +<created>date(YYYY-MM-GG HH:MM:SS)</created> +<edited>date(YYYY-MM-GG HH:MM:SS)</edited> +<title>photo title</title> +<desc>photo description</desc> +<album>album name</album> +<filename>original file name</filename> +<type>mime type</type> +<height>number</height> +<width>number</width> +<profile>1 if is profile photo</profile> +<links type="array"> +<link type="mime type" scale="scale number" href="image url"/> +... +</links> +</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 +``` +<photos type="array"> +<photo id="resource_id" +album="album name" +filename="original file name" +type="image mime type"> +"url to thumb sized image" +</photo> +... +</photos> +``` + + --- ## Not Implemented API calls The following API calls are implemented in GNU Social but not in Friendica: (incomplete) @@ -702,13 +778,13 @@ The following API calls from the Twitter API aren't implemented neither in Frien ### 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" +/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)) +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. From a283691149fa9224be8b7ba8edc4dcdef88c3612 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Mon, 8 Feb 2016 10:34:27 +0100 Subject: [PATCH 062/273] api docs: fix indentation --- doc/api.md | 132 ++++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/doc/api.md b/doc/api.md index f050ae4304..c020f403ff 100644 --- a/doc/api.md +++ b/doc/api.md @@ -52,20 +52,20 @@ Error body is json: ``` -{ -"error": "Specific error message", -"request": "API path requested", -"code": "HTTP error code" -} + { + "error": "Specific error message", + "request": "API path requested", + "code": "HTTP error code" + } ``` xml: ``` -<status> -<error>Specific error message</error> -<request>API path requested</request> -<code>HTTP error code</code> -</status> + <status> + <error>Specific error message</error> + <request>API path requested</request> + <code>HTTP error code</code> + </status> ``` --- @@ -605,47 +605,47 @@ An image used as profile image has only scale 4-6, other images only 0-3 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": { -"<scale>": "url to image" -... -}, -// if 'scale' is set -"datasize": "size in byte", -"data": "base64 encoded image data" -} + { + "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": { + "<scale>": "url to image" + ... + }, + // if 'scale' is set + "datasize": "size in byte", + "data": "base64 encoded image data" + } ``` xml ``` -<photo> -<id>photo id</id> -<created>date(YYYY-MM-GG HH:MM:SS)</created> -<edited>date(YYYY-MM-GG HH:MM:SS)</edited> -<title>photo title</title> -<desc>photo description</desc> -<album>album name</album> -<filename>original file name</filename> -<type>mime type</type> -<height>number</height> -<width>number</width> -<profile>1 if is profile photo</profile> -<links type="array"> -<link type="mime type" scale="scale number" href="image url"/> -... -</links> -</photo> + <photo> + <id>photo id</id> + <created>date(YYYY-MM-GG HH:MM:SS)</created> + <edited>date(YYYY-MM-GG HH:MM:SS)</edited> + <title>photo title</title> + <desc>photo description</desc> + <album>album name</album> + <filename>original file name</filename> + <type>mime type</type> + <height>number</height> + <width>number</width> + <profile>1 if is profile photo</profile> + <links type="array"> + <link type="mime type" scale="scale number" href="image url"/> + ... + </links> + </photo> ``` --- @@ -657,29 +657,29 @@ Returns a list of all photo resources of the logged in user. json ``` -[ -{ -id: "resource_id", -album: "album name", -filename: "original file name", -type: "image mime type", -thumb: "url to thumb sized image" -}, -... -] + [ + { + id: "resource_id", + album: "album name", + filename: "original file name", + type: "image mime type", + thumb: "url to thumb sized image" + }, + ... + ] ``` xml ``` -<photos type="array"> -<photo id="resource_id" -album="album name" -filename="original file name" -type="image mime type"> -"url to thumb sized image" -</photo> -... -</photos> + <photos type="array"> + <photo id="resource_id" + album="album name" + filename="original file name" + type="image mime type"> + "url to thumb sized image" + </photo> + ... + </photos> ``` From 9ff0fc92dd1525450ea99347895fbbe5e367d56b Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Mon, 8 Feb 2016 13:42:06 +0100 Subject: [PATCH 063/273] NotificationsManager: add backtick to queries --- include/NotificationsManager.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/NotificationsManager.php b/include/NotificationsManager.php index b7e31d4f65..c99d00742c 100644 --- a/include/NotificationsManager.php +++ b/include/NotificationsManager.php @@ -79,7 +79,7 @@ class NotificationsManager { if ($limit!="") $limit = " LIMIT ".$limit; - $r = q("SELECT * FROM notify WHERE uid = %d $filter_sql ORDER BY $order_sql $limit", + $r = q("SELECT * FROM `notify` WHERE `uid` = %d $filter_sql ORDER BY $order_sql $limit", intval(local_user()) ); if ($r!==false && count($r)>0) return $this->_set_extra($r); @@ -93,7 +93,7 @@ class NotificationsManager { * @return array note values or null if not found */ public function getByID($id) { - $r = q("SELECT * FROM notify WHERE id = %d AND uid = %d LIMIT 1", + $r = q("SELECT * FROM `notify` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($id), intval(local_user()) ); @@ -111,7 +111,7 @@ class NotificationsManager { * @return bool true on success, false on errors */ public function setSeen($note, $seen = true) { - return q("UPDATE notify SET seen = %d WHERE ( link = '%s' OR ( parent != 0 AND parent = %d AND otype = '%s' )) AND uid = %d", + return q("UPDATE `notify` SET `seen` = %d WHERE ( `link` = '%s' OR ( `parent` != 0 AND `parent` = %d AND `otype` = '%s' )) AND `uid` = %d", intval($seen), dbesc($note['link']), intval($note['parent']), @@ -127,7 +127,7 @@ class NotificationsManager { * @return bool true on success, false on error */ public function setAllSeen($seen = true) { - return q("UPDATE notify SET seen = %d WHERE uid = %d", + return q("UPDATE `notify` SET `seen` = %d WHERE `uid` = %d", intval($seen), intval(local_user()) ); From 2a016e7685e76b29641dce09172058f716f4ee4b Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Mon, 8 Feb 2016 14:35:41 +0100 Subject: [PATCH 064/273] add missing query backticks --- include/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/api.php b/include/api.php index 16481201e3..d205451e5e 100644 --- a/include/api.php +++ b/include/api.php @@ -3457,7 +3457,7 @@ $nm->setSeen($note); if ($note['otype']=='item') { // would be really better with an ItemsManager and $im->getByID() :-P - $r = q("SELECT * FROM item WHERE id=%d AND uid=%d", + $r = q("SELECT * FROM `item` WHERE `id`=%d AND `uid`=%d", intval($note['iid']), intval(local_user()) ); From cc18fd7ad90ed34c2dda33492d3e9b6c0cbd44a3 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Mon, 8 Feb 2016 15:00:53 +0100 Subject: [PATCH 065/273] contactedit-actions-button: doing some availability checks for the actions --- mod/contacts.php | 119 ++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/mod/contacts.php b/mod/contacts.php index 248ed47ab3..53fb96c5b1 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -565,7 +565,7 @@ function contacts_content(&$a) { ($contact['rel'] == CONTACT_IS_FOLLOWER)) $follow = $a->get_baseurl(true)."/follow?url=".urlencode($contact["url"]); - $contact_actions = contact_action_menu($contact); + $contact_actions = contact_actions($contact); $o .= replace_macros($tpl, array( @@ -959,65 +959,78 @@ function _contact_detail_for_template($rr){ } -function contact_action_menu($contact) { +/** + * @brief Gives a array with actions which can performed to a given contact + * + * This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others + * + * @param array $contact Data about the Contact + * @return array with actions related actions + */ +function contact_actions($contact) { - $contact_action_menu = array( - 'suggest' => array( - 'label' => t('Suggest friends'), - 'url' => app::get_baseurl(true) . '/fsuggest/' . $contact['id'], - 'title' => '', - 'sel' => '', - 'id' => 'suggest', - ), + $poll_enabled = in_array($contact['network'], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_FEED, NETWORK_MAIL, NETWORK_MAIL2)); + $contact_action_menu = array(); - 'update' => array( - 'label' => t('Update now'), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/update', - 'title' => '', - 'sel' => '', - 'id' => 'update', - ), + if($contact['network'] === NETWORK_DFRN) { + $contact_actions['suggest'] = array( + 'label' => t('Suggest friends'), + 'url' => app::get_baseurl(true) . '/fsuggest/' . $contact['id'], + 'title' => '', + 'sel' => '', + 'id' => 'suggest', + ); + } - 'repair' => array( - 'label' => t('Repair'), - 'url' => app::get_baseurl(true) . '/crepair/' . $contact['id'], - 'title' => t('Advanced Contact Settings'), - 'sel' => '', - 'id' => 'repair', - ), + if($poll_enabled) { + $contact_actions['update'] = array( + 'label' => t('Update now'), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/update', + 'title' => '', + 'sel' => '', + 'id' => 'update', + ); + } - 'block' => array( - 'label' => (intval($contact['blocked']) ? t('Unblock') : t('Block') ), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/block', - 'title' => t('Toggle Blocked status'), - 'sel' => (intval($contact['blocked']) ? 'active' : ''), - 'id' => 'toggle-block', - ), + $contact_actions['repair'] = array( + 'label' => t('Repair'), + 'url' => app::get_baseurl(true) . '/crepair/' . $contact['id'], + 'title' => t('Advanced Contact Settings'), + 'sel' => '', + 'id' => 'repair', + ); - 'ignore' => array( - 'label' => (intval($contact['readonly']) ? t('Unignore') : t('Ignore') ), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/ignore', - 'title' => t('Toggle Ignored status'), - 'sel' => (intval($contact['readonly']) ? 'active' : ''), - 'id' => 'toggle-ignore', - ), + $contact_actions['block'] = array( + 'label' => (intval($contact['blocked']) ? t('Unblock') : t('Block') ), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/block', + 'title' => t('Toggle Blocked status'), + 'sel' => (intval($contact['blocked']) ? 'active' : ''), + 'id' => 'toggle-block', + ); - 'archive' => array( - 'label' => (intval($contact['archive']) ? t('Unarchive') : t('Archive') ), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/archive', - 'title' => t('Toggle Archive status'), - 'sel' => (intval($contact['archive']) ? 'active' : ''), - 'id' => 'toggle-archive', - ), + $contact_actions['ignore'] = array( + 'label' => (intval($contact['readonly']) ? t('Unignore') : t('Ignore') ), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/ignore', + 'title' => t('Toggle Ignored status'), + 'sel' => (intval($contact['readonly']) ? 'active' : ''), + 'id' => 'toggle-ignore', + ); - 'delete' => array( - 'label' => t('Delete'), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/drop', - 'title' => t('Delete contact'), - 'sel' => '', - 'id' => 'delete', - ) - ); + $contact_actions['archive'] = array( + 'label' => (intval($contact['archive']) ? t('Unarchive') : t('Archive') ), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/archive', + 'title' => t('Toggle Archive status'), + 'sel' => (intval($contact['archive']) ? 'active' : ''), + 'id' => 'toggle-archive', + ); + + $contact_actions['delete'] = array( + 'label' => t('Delete'), + 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/drop', + 'title' => t('Delete contact'), + 'sel' => '', + 'id' => 'delete', + ); return $contact_action_menu; } From b3f7e8d38808080a181f24720951842c0c76485e Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 8 Feb 2016 15:26:24 +0100 Subject: [PATCH 066/273] Now the public static functions are public static functions --- include/dfrn.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 84660a0d18..c6c8deef58 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -40,7 +40,7 @@ class dfrn { * * @return string DFRN entries */ - function entries($items,$owner) { + public static function entries($items,$owner) { $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; @@ -70,7 +70,7 @@ class dfrn { * * @return string DFRN feed entries */ - function feed($dfrn_id, $owner_nick, $last_update, $direction = 0) { + public static function feed($dfrn_id, $owner_nick, $last_update, $direction = 0) { $a = get_app(); @@ -277,7 +277,7 @@ class dfrn { * * @return string DFRN mail */ - function mail($item, $owner) { + public static function mail($item, $owner) { $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; @@ -311,7 +311,7 @@ class dfrn { * * @return string DFRN suggestions */ - function fsuggest($item, $owner) { + public static function fsuggest($item, $owner) { $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; @@ -338,7 +338,7 @@ class dfrn { * * @return string DFRN relocations */ - function relocate($owner, $uid) { + public static function relocate($owner, $uid) { /* get site pubkey. this could be a new installation with no site keys*/ $pubkey = get_config('system','site_pubkey'); @@ -846,7 +846,7 @@ class dfrn { * * @return int Deliver status. -1 means an error. */ - function deliver($owner,$contact,$atom, $dissolve = false) { + public static function deliver($owner,$contact,$atom, $dissolve = false) { $a = get_app(); @@ -2319,7 +2319,7 @@ class dfrn { * @param array $importer Record of the importer user mixed with contact of the content * @param bool $sort_by_date Is used when feeds are polled */ - function import($xml,$importer, $sort_by_date = false) { + public static function import($xml,$importer, $sort_by_date = false) { if ($xml == "") return; From 981aad46d3fd926ff118bc63e7eb66451cac766f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 8 Feb 2016 22:37:29 +0100 Subject: [PATCH 067/273] DFRN: Sending pokes does work now again --- include/dfrn.php | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index c6c8deef58..c4999a08e1 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -634,13 +634,20 @@ class dfrn { $r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link); - $data = parse_xml_string($r->link, false); - foreach ($data->attributes() AS $parameter => $value) - $attributes[$parameter] = $value; - } else + // XML does need a single element as root element so we add a dummy element here + $data = parse_xml_string("<dummy>".$r->link."</dummy>", false); + if (is_object($data)) { + foreach ($data->link AS $link) { + $attributes = array(); + foreach ($link->attributes() AS $parameter => $value) + $attributes[$parameter] = $value; + xml_add_element($doc, $entry, "link", "", $attributes); + } + } + } else { $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link); - - xml_add_element($doc, $entry, "link", "", $attributes); + xml_add_element($doc, $entry, "link", "", $attributes); + } } if($r->content) xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html")); @@ -1311,9 +1318,10 @@ class dfrn { if (is_object($title)) $obj_element->appendChild($obj_doc->importNode($title, true)); - $link = $xpath->query("atom:link", $activity)->item(0); - if (is_object($link)) - $obj_element->appendChild($obj_doc->importNode($link, true)); + $links = $xpath->query("atom:link", $activity); + if (is_object($links)) + foreach ($links AS $link) + $obj_element->appendChild($obj_doc->importNode($link, true)); $content = $xpath->query("atom:content", $activity)->item(0); if (is_object($content)) From 0b5d7b300e90324d0931837550cac5a9ed348f97 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Mon, 8 Feb 2016 23:15:20 +0100 Subject: [PATCH 068/273] contactedit-actions-button: adjust the themes and template polishing --- mod/contacts.php | 9 +- view/global.css | 30 +++- view/templates/contact_edit.tpl | 167 ++++++++---------- view/theme/duepuntozero/style.css | 43 +++-- view/theme/frost-mobile/js/main.js | 1 - view/theme/frost-mobile/style.css | 58 +++++- .../frost-mobile/templates/contact_edit.tpl | 83 +++++---- view/theme/frost/style.css | 64 ++++++- view/theme/frost/templates/contact_edit.tpl | 84 +++++---- view/theme/quattro/dark/style.css | 5 +- view/theme/quattro/green/style.css | 5 +- view/theme/quattro/lilac/style.css | 11 +- view/theme/quattro/quattro.less | 29 +-- view/theme/smoothly/style.css | 48 +++-- view/theme/vier/style.css | 132 +++++++++----- view/theme/vier/templates/contact_edit.tpl | 99 +++++++++++ 16 files changed, 584 insertions(+), 284 deletions(-) create mode 100644 view/theme/vier/templates/contact_edit.tpl diff --git a/mod/contacts.php b/mod/contacts.php index 53fb96c5b1..77176f6ca6 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -565,6 +565,7 @@ function contacts_content(&$a) { ($contact['rel'] == CONTACT_IS_FOLLOWER)) $follow = $a->get_baseurl(true)."/follow?url=".urlencode($contact["url"]); + // Load contactact related actions like hide, suggest, delete and others $contact_actions = contact_actions($contact); @@ -586,7 +587,7 @@ function contacts_content(&$a) { '$lblcrepair' => t("Repair URL settings"), '$lblrecent' => t('View conversations'), '$lblsuggest' => $lblsuggest, - '$delete' => t('Delete contact'), + //'$delete' => t('Delete contact'), '$nettype' => $nettype, '$poll_interval' => $poll_interval, '$poll_enabled' => $poll_enabled, @@ -627,6 +628,8 @@ function contacts_content(&$a) { '$keywords_label' => t("Tags:"), '$contact_action_button' => t("Actions"), '$contact_actions' => $contact_actions, + '$contact_status' => t("Status"), + '$contact_settings_label' => t('Contact Settings'), )); @@ -970,7 +973,7 @@ function _contact_detail_for_template($rr){ function contact_actions($contact) { $poll_enabled = in_array($contact['network'], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_FEED, NETWORK_MAIL, NETWORK_MAIL2)); - $contact_action_menu = array(); + $contact_action = array(); if($contact['network'] === NETWORK_DFRN) { $contact_actions['suggest'] = array( @@ -1032,5 +1035,5 @@ function contact_actions($contact) { 'id' => 'delete', ); - return $contact_action_menu; + return $contact_actions; } diff --git a/view/global.css b/view/global.css index df001ed362..41af643ecc 100644 --- a/view/global.css +++ b/view/global.css @@ -15,12 +15,16 @@ padding: 5px 10px; text-align: center; } +a.btn, a.btn:hover { + text-decoration: none; + color: inherit; +} -ul.menu-popup li.divider { +.menu-popup .divider { height: 1px; margin: 3px 0; overflow: hidden; - background-color: #2d2d2d;; + background-color: #2d2d2d; } /* List of social Networks */ @@ -375,6 +379,24 @@ td.federation-data { } /* contact-edit */ +#contact-edit-status-wrapper { + border: 1px solid; + padding: 10px; +} #contact-edit-actions { - float: right; -} \ No newline at end of file + float: right; + display: inline-block; + position: relative; +} +#contact-edit-actions > .menu-popup { + right: 0; + left: auto; +} + +#contact-edit-settings-label:after { + content: ' »'; +} + +#contact-edit-settings { + display: none; +} diff --git a/view/templates/contact_edit.tpl b/view/templates/contact_edit.tpl index 682ebfa36a..91ef451278 100644 --- a/view/templates/contact_edit.tpl +++ b/view/templates/contact_edit.tpl @@ -1,110 +1,99 @@ + {{if $header}}<h2>{{$header}}</h2>{{/if}} <div id="contact-edit-wrapper" > + {{* Insert Tab-Nav *}} {{$tab_str}} - <div id="contact-edit-actions"> - <a class="btn" rel="#contact-actions-menu" href="#" id="contact-edit-actions-button">{{$contact_action_button}}</a> - - <ul role="menu" aria-haspopup="true" id="contact-actions-menu" class="menu-popup" > - {{if $lblsuggest}}<li><a href="#" title="{{$contact_actions.suggest.title}}" onclick="window.location.href='{{$contact_actions.suggest.url}}'; return false;">{{$contact_actions.suggest.label}}</a></li>{{/if}} - {{if $poll_enabled}}<li><a href="#" title="{{$contact_actions.update.title}}" onclick="window.location.href='{{$contact_actions.update.url}}'; return false;">{{$contact_actions.update.label}}</a></li>{{/if}} - <li><a href="#" title="{{$contact_actions.repair.title}}" onclick="window.location.href='{{$contact_actions.repair.url}}'; return false;">{{$contact_actions.repair.label}}</a></li> - <li class="divider"></li> - <li><a href="#" title="{{$contact_actions.block.title}}" onclick="window.location.href='{{$contact_actions.block.url}}'; return false;">{{$contact_actions.block.label}}</a></li> - <li><a href="#" title="{{$contact_actions.ignore.title}}" onclick="window.location.href='{{$contact_actions.ignore.url}}'; return false;">{{$contact_actions.ignore.label}}</a></li> - <li><a href="#" title="{{$contact_actions.archive.title}}" onclick="window.location.href='{{$contact_actions.archive.url}}'; return false;">{{$contact_actions.archive.label}}</a></li> - <li><a href="#" title="{{$contact_actions.delete.title}}" onclick="return confirmDelete();">{{$contact_actions.delete.label}}</a></li> - </ul> - </div> - - <div id="contact-edit-drop-link" > - <a href="contacts/{{$contact_id}}/drop" class="icon drophide" id="contact-edit-drop-link" onclick="return confirmDelete();" title="{{$delete}}" onmouseover="imgbright(this);" onmouseout="imgdull(this);"></a> - </div> - - <div id="contact-edit-drop-link-end"></div> - <div id="contact-edit-nav-wrapper" > <div id="contact-edit-links"> - <ul> - {{if $relation_text}} - <li><div id="contact-edit-rel">{{$relation_text}}</div></li> - {{/if}} - {{if $lost_contact}} - <li><div id="lost-contact-message">{{$lost_contact}}</div></li> - {{/if}} - {{if $insecure}} - <li><div id="insecure-message">{{$insecure}}</div></li> - {{/if}} - {{if $blocked}} - <li><div id="block-message">{{$blocked}}</div></li> - {{/if}} - {{if $ignored}} - <li><div id="ignore-message">{{$ignored}}</div></li> - {{/if}} - {{if $archived}} - <li><div id="archive-message">{{$archived}}</div></li> - {{/if}} - </ul> + <div id="contact-edit-status-wrapper"> + <span id="contact-edit-contact-status">{{$contact_status}}</span> - <ul> - <!-- <li><a href="network/0?nets=all&cid={{$contact_id}}" id="contact-edit-view-recent">{{$lblrecent}}</a></li> --> - {{if $lblsuggest}} - <li><a href="fsuggest/{{$contact_id}}" id="contact-edit-suggest">{{$lblsuggest}}</a></li> - {{/if}} - {{if $follow}} - <li><div id="contact-edit-follow"><a href="{{$follow}}">{{$follow_text}}</a></div></li> - {{/if}} + {{* This is the Action menu where contact related actions like 'ignore', 'hide' can be performed *}} + <div id="contact-edit-actions"> + <a class="btn" rel="#contact-actions-menu" href="#" id="contact-edit-actions-button">{{$contact_action_button}}</a> - </ul> + <ul role="menu" aria-haspopup="true" id="contact-actions-menu" class="menu-popup" > + {{if $lblsuggest}}<li role="menuitem"><a href="#" title="{{$contact_actions.suggest.title}}" onclick="window.location.href='{{$contact_actions.suggest.url}}'; return false;">{{$contact_actions.suggest.label}}</a></li>{{/if}} + {{if $poll_enabled}}<li role="menuitem"><a href="#" title="{{$contact_actions.update.title}}" onclick="window.location.href='{{$contact_actions.update.url}}'; return false;">{{$contact_actions.update.label}}</a></li>{{/if}} + <li role="menuitem"><a href="#" title="{{$contact_actions.repair.title}}" onclick="window.location.href='{{$contact_actions.repair.url}}'; return false;">{{$contact_actions.repair.label}}</a></li> + <li class="divider"></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.block.title}}" onclick="window.location.href='{{$contact_actions.block.url}}'; return false;">{{$contact_actions.block.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.ignore.title}}" onclick="window.location.href='{{$contact_actions.ignore.url}}'; return false;">{{$contact_actions.ignore.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.archive.title}}" onclick="window.location.href='{{$contact_actions.archive.url}}'; return false;">{{$contact_actions.archive.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.delete.title}}" onclick="return confirmDelete();">{{$contact_actions.delete.label}}</a></li> + </ul> + </div> + {{* Block with status information about the contact *}} + <ul> + {{if $relation_text}}<li><div id="contact-edit-rel">{{$relation_text}}</div></li>{{/if}} + + {{if $poll_enabled}} + <li><div id="contact-edit-last-update-text">{{$lastupdtext}} <span id="contact-edit-last-updated">{{$last_update}}</span></div> + {{if $poll_interval}} + <span id="contact-edit-poll-text">{{$updpub}}</span> {{$poll_interval}} + {{/if}} + </li> + {{/if}} + + {{if $lost_contact}}<li><div id="lost-contact-message">{{$lost_contact}}</div></li>{{/if}} + {{if $insecure}}<li><div id="insecure-message">{{$insecure}}</div></li> {{/if}} + {{if $blocked}}<li><div id="block-message">{{$blocked}}</div></li>{{/if}} + {{if $ignored}}<li><div id="ignore-message">{{$ignored}}</div></li>{{/if}} + {{if $archived}}<li><div id="archive-message">{{$archived}}</div></li>{{/if}} + </ul> + + <ul> + <!-- <li><a href="network/0?nets=all&cid={{$contact_id}}" id="contact-edit-view-recent">{{$lblrecent}}</a></li> --> + {{if $follow}}<li><div id="contact-edit-follow"><a href="{{$follow}}">{{$follow_text}}</a></div></li>{{/if}} + </ul> + </div> {{* End of contact-edit-status-wrapper *}} + + {{* Some information about the contact from the profile *}} <dl><dt>{{$profileurllabel}}</dt><dd><a target="blank" href="{{$url}}">{{$profileurl}}</a></dd></dl> {{if $location}}<dl><dt>{{$location_label}}</dt><dd>{{$location}}</dd></dl>{{/if}} {{if $keywords}}<dl><dt>{{$keywords_label}}</dt><dd>{{$keywords}}</dd></dl>{{/if}} {{if $about}}<dl><dt>{{$about_label}}</dt><dd>{{$about}}</dd></dl>{{/if}} - </div> - </div> - <div id="contact-edit-nav-end"></div> + </div>{{* End of contact-edit-links *}} -<hr /> + <div id="contact-edit-links-end"></div> -<form action="contacts/{{$contact_id}}" method="post" > -<input type="hidden" name="contact_id" value="{{$contact_id}}"> + <hr /> - <div id="contact-edit-poll-wrapper"> - {{if $poll_enabled}} - <div id="contact-edit-last-update-text">{{$lastupdtext}} <span id="contact-edit-last-updated">{{$last_update}}</span></div> - {{if $poll_interval}} - <span id="contact-edit-poll-text">{{$updpub}}</span> {{$poll_interval}} + <h4 id="contact-edit-settings-label" class="fakelink" onclick="openClose('contact-edit-settings')">{{$contact_settings_label}}</h4> + <div id="contact-edit-settings"> + <form action="contacts/{{$contact_id}}" method="post" > + <input type="hidden" name="contact_id" value="{{$contact_id}}"> + + <div id="contact-edit-end" ></div> + {{include file="field_checkbox.tpl" field=$notify}} + {{if $fetch_further_information}} + {{include file="field_select.tpl" field=$fetch_further_information}} + {{if $fetch_further_information.2 == 2 }} {{include file="field_textarea.tpl" field=$ffi_keyword_blacklist}} {{/if}} + {{/if}} + {{include file="field_checkbox.tpl" field=$hidden}} + + <div id="contact-edit-info-wrapper"> + <h4>{{$lbl_info1}}</h4> + <textarea id="contact-edit-info" rows="8" cols="60" name="info">{{$info}}</textarea> + <input class="contact-edit-submit" type="submit" name="submit" value="{{$submit|escape:'html'}}" /> + </div> + <div id="contact-edit-info-end"></div> + + {{if $profile_select}} + <div id="contact-edit-profile-select-text"> + <h4>{{$lbl_vis1}}</h4> + <p>{{$lbl_vis2}}</p> + </div> + {{$profile_select}} + <div id="contact-edit-profile-select-end"></div> + <input class="contact-edit-submit" type="submit" name="submit" value="{{$submit|escape:'html'}}" /> {{/if}} - <span id="contact-edit-update-now" class="button"><a href="contacts/{{$contact_id}}/update" >{{$udnow}}</a></span> - {{/if}} - </div> - <div id="contact-edit-end" ></div> - {{include file="field_checkbox.tpl" field=$notify}} - {{if $fetch_further_information}} - {{include file="field_select.tpl" field=$fetch_further_information}} - {{if $fetch_further_information.2 == 2 }} {{include file="field_textarea.tpl" field=$ffi_keyword_blacklist}} {{/if}} - {{/if}} - {{include file="field_checkbox.tpl" field=$hidden}} - -<div id="contact-edit-info-wrapper"> -<h4>{{$lbl_info1}}</h4> - <textarea id="contact-edit-info" rows="8" cols="60" name="info">{{$info}}</textarea> - <input class="contact-edit-submit" type="submit" name="submit" value="{{$submit|escape:'html'}}" /> -</div> -<div id="contact-edit-info-end"></div> - -{{if $profile_select}} - <div id="contact-edit-profile-select-text"> - <h4>{{$lbl_vis1}}</h4> - <p>{{$lbl_vis2}}</p> - </div> - {{$profile_select}} - <div id="contact-edit-profile-select-end"></div> - <input class="contact-edit-submit" type="submit" name="submit" value="{{$submit|escape:'html'}}" /> -{{/if}} -</form> + </form> + </div> + </div>{{* End of contact-edit-nav-wrapper *}} </div> diff --git a/view/theme/duepuntozero/style.css b/view/theme/duepuntozero/style.css index c004eb53d0..787e52600c 100644 --- a/view/theme/duepuntozero/style.css +++ b/view/theme/duepuntozero/style.css @@ -83,6 +83,26 @@ blockquote { margin-right: 5px; } +ul.menu-popup { + position: absolute; + display: none; + width: auto; + margin: 2px 0 0; + padding: 0px; + list-style: none; + z-index: 100000; + border: 2px solid #444444; + background: #FFFFFF; +} +.menu-popup li a { + padding: 2px; + white-space: nowrap; +} + +a.btn, a.btn:hover { + text-decoration: none; + color: inherit; +} /* nav */ @@ -140,12 +160,12 @@ nav #banner #logo-text a:hover { text-decoration: none; } .nav-commlink, .nav-login-link { - display: block; - height: 15px; + display: block; + height: 15px; margin-top: 67px; margin-right: 2px; - //padding: 6px 10px; - padding: 6px 3px; + /*padding: 6px 10px;*/ + padding: 6px 3px; float: left; bottom: 140px; border: 1px solid #babdb6; @@ -244,7 +264,7 @@ section { display:block; float:left; padding: 0.4em; - //margin-right: 1em; + /*margin-right: 1em;*/ margin-right: 3px ; } .tab.active { @@ -3371,17 +3391,6 @@ div.jGrowl div.info { .nav-notify.show { display: block; } -ul.menu-popup { - position: absolute; - display: none; - width: 10em; - margin: 0px; - padding: 0px; - list-style: none; - z-index: 100000; - top: 90px; - left: 200px; -} #nav-notifications-menu { width: 320px; max-height: 400px; @@ -3391,6 +3400,8 @@ ul.menu-popup { -webkit-border-radius: 5px; border-radius:5px; border: 1px solid #888; + top: 90px; + left: 200px; } #nav-notifications-menu .contactname { font-weight: bold; font-size: 0.9em; } #nav-notifications-menu img { float: left; margin-right: 5px; } diff --git a/view/theme/frost-mobile/js/main.js b/view/theme/frost-mobile/js/main.js index 9eac71be83..7e2880594d 100644 --- a/view/theme/frost-mobile/js/main.js +++ b/view/theme/frost-mobile/js/main.js @@ -13,7 +13,6 @@ if($(listID).is(":visible")) { $(listID).hide(); $(listID+"-wrapper").show(); - alert($(listID+"-wrapper").attr("id")); } else { $(listID).show(); diff --git a/view/theme/frost-mobile/style.css b/view/theme/frost-mobile/style.css index 400b23c10b..a99cc17a91 100644 --- a/view/theme/frost-mobile/style.css +++ b/view/theme/frost-mobile/style.css @@ -139,6 +139,47 @@ blockquote { margin-right: 5px; } +.btn { + outline: none; + -moz-box-shadow: inset 0px 1px 0px 0px #ffffff; + -webkit-box-shadow: inset 0px 1px 0px 0px #ffffff; + box-shadow: inset 0px 1px 0px 0px #ffffff; + background-color: #ededed; + text-indent: 0; + border: 1px solid #dcdcdc; + display: inline-block; + color: #777777; + padding: 5px 10px; + text-align: center; + border-radius: 8px; +} + +.menu-popup { + width: auto; + border: 2px solid #444444; + background: #FFFFFF; + position: absolute; + margin: 2px 0 0; + display: none; + z-index: 10000; +} + +.menu-popup li a { + display: block; + padding: 2px; +} + +.menu-popup li a:hover { + color: #FFFFFF; + background: #3465A4; + text-decoration: none; +} +ul.menu-popup li.divider { + height: 1px; + margin: 3px 0; + overflow: hidden; + background-color: #2d2d2d; +} /* nav */ @@ -2045,6 +2086,19 @@ input#profile-jot-email { margin-left: 15px; } +#contact-edit-status-wrapper { + padding: 10px; + border: 1px solid #aaa; + border-radius: 8px; +} + +#contact-edit-contact-status { + font-weight: bold; +} +#contact-edit-actions { + float: right; + display: inline-block; +} #contact-edit-wrapper { margin-top: 10px; } @@ -2059,14 +2113,10 @@ input#profile-jot-email { } #contact-edit-last-update-text { - float: left; - clear: left; margin-top: 30px; } #contact-edit-poll-text { - float: left; - clear: left; margin-top: 15px; margin-bottom: 0px; } diff --git a/view/theme/frost-mobile/templates/contact_edit.tpl b/view/theme/frost-mobile/templates/contact_edit.tpl index e6401de606..48df4a0286 100644 --- a/view/theme/frost-mobile/templates/contact_edit.tpl +++ b/view/theme/frost-mobile/templates/contact_edit.tpl @@ -6,12 +6,6 @@ {{$tab_str}} - <div id="contact-edit-drop-link" > - <a href="contacts/{{$contact_id}}/drop" class="icon drophide" id="contact-edit-drop-link" onclick="return confirmDelete();" title="{{$delete}}" {{*onmouseover="imgbright(this);" onmouseout="imgdull(this);"*}}></a> - </div> - - <div id="contact-edit-drop-link-end"></div> - <div class="vcard"> <div class="fn">{{$name}}</div> <div id="profile-photo-wrapper"><img class="photo" style="width: 175px; height: 175px;" src="{{$photo}}" alt="{{$name}}" /></div> @@ -20,41 +14,50 @@ <div id="contact-edit-nav-wrapper" > <div id="contact-edit-links"> - <ul> - <li><div id="contact-edit-rel">{{$relation_text}}</div></li> - <li><div id="contact-edit-nettype">{{$nettype}}</div></li> - {{if $lost_contact}} - <li><div id="lost-contact-message">{{$lost_contact}}</div></li> - {{/if}} - {{if $insecure}} - <li><div id="insecure-message">{{$insecure}}</div></li> - {{/if}} - {{if $blocked}} - <li><div id="block-message">{{$blocked}}</div></li> - {{/if}} - {{if $ignored}} - <li><div id="ignore-message">{{$ignored}}</div></li> - {{/if}} - {{if $archived}} - <li><div id="archive-message">{{$archived}}</div></li> - {{/if}} + <div id="contact-edit-status-wrapper"> + <span id="contact-edit-contact-status">{{$contact_status}}</span> - <li> </li> + <div id="contact-edit-actions"> + <div class="btn" id="contact-edit-actions-button" onclick="openClose('contact-actions-menu')">{{$contact_action_button}}</div> - {{if $common_text}} - <li><div id="contact-edit-common"><a href="{{$common_link}}">{{$common_text}}</a></div></li> - {{/if}} - {{if $all_friends}} - <li><div id="contact-edit-allfriends"><a href="allfriends/{{$contact_id}}">{{$all_friends}}</a></div></li> - {{/if}} + <ul role="menu" aria-haspopup="true" id="contact-actions-menu" class="menu-popup" > + {{if $lblsuggest}}<li role="menuitem"><a href="#" title="{{$contact_actions.suggest.title}}" onclick="window.location.href='{{$contact_actions.suggest.url}}'; return false;">{{$contact_actions.suggest.label}}</a></li>{{/if}} + {{if $poll_enabled}}<li role="menuitem"><a href="#" title="{{$contact_actions.update.title}}" onclick="window.location.href='{{$contact_actions.update.url}}'; return false;">{{$contact_actions.update.label}}</a></li>{{/if}} + <li role="menuitem"><a href="#" title="{{$contact_actions.repair.title}}" onclick="window.location.href='{{$contact_actions.repair.url}}'; return false;">{{$contact_actions.repair.label}}</a></li> + <li class="divider"></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.block.title}}" onclick="window.location.href='{{$contact_actions.block.url}}'; return false;">{{$contact_actions.block.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.ignore.title}}" onclick="window.location.href='{{$contact_actions.ignore.url}}'; return false;">{{$contact_actions.ignore.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.archive.title}}" onclick="window.location.href='{{$contact_actions.archive.url}}'; return false;">{{$contact_actions.archive.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.delete.title}}" onclick="return confirmDelete();">{{$contact_actions.delete.label}}</a></li> + </ul> + </div> + <ul> + <li><div id="contact-edit-rel">{{$relation_text}}</div></li> + <li><div id="contact-edit-nettype">{{$nettype}}</div></li> + {{if $poll_enabled}} + <div id="contact-edit-poll-wrapper"> + <div id="contact-edit-last-update-text">{{$lastupdtext}} <span id="contact-edit-last-updated">{{$last_update}}</span></div> + </div> + {{/if}} + {{if $lost_contact}} + <li><div id="lost-contact-message">{{$lost_contact}}</div></li> + {{/if}} + {{if $insecure}} + <li><div id="insecure-message">{{$insecure}}</div></li> + {{/if}} + {{if $blocked}} + <li><div id="block-message">{{$blocked}}</div></li> + {{/if}} + {{if $ignored}} + <li><div id="ignore-message">{{$ignored}}</div></li> + {{/if}} + {{if $archived}} + <li><div id="archive-message">{{$archived}}</div></li> + {{/if}} - <li><a href="network/0?nets=all&cid={{$contact_id}}" id="contact-edit-view-recent">{{$lblrecent}}</a></li> - {{if $lblsuggest}} - <li><a href="fsuggest/{{$contact_id}}" id="contact-edit-suggest">{{$lblsuggest}}</a></li> - {{/if}} - - </ul> + </ul> + </div> </div> </div> <div id="contact-edit-nav-end"></div> @@ -63,12 +66,6 @@ <form action="contacts/{{$contact_id}}" method="post" > <input type="hidden" name="contact_id" value="{{$contact_id}}"> - {{if $poll_enabled}} - <div id="contact-edit-poll-wrapper"> - <div id="contact-edit-last-update-text">{{$lastupdtext}} <span id="contact-edit-last-updated">{{$last_update}}</span></div> - <span id="contact-edit-poll-text">{{$updpub}} {{$poll_interval}}</span> <span id="contact-edit-update-now" class="button"><a id="update_now_link" href="contacts/{{$contact_id}}/update" >{{$udnow}}</a></span> - </div> - {{/if}} <div id="contact-edit-end" ></div> {{include file="field_checkbox.tpl" field=$hidden}} diff --git a/view/theme/frost/style.css b/view/theme/frost/style.css index 3dd400c76b..1054b55c11 100644 --- a/view/theme/frost/style.css +++ b/view/theme/frost/style.css @@ -113,6 +113,51 @@ blockquote { .pull-right { float: right } +.btn { + outline: none; + -moz-box-shadow: inset 0px 1px 0px 0px #ffffff; + -webkit-box-shadow: inset 0px 1px 0px 0px #ffffff; + box-shadow: inset 0px 1px 0px 0px #ffffff; + background-color: #ededed; + text-indent: 0; + border: 1px solid #dcdcdc; + display: inline-block; + color: #777777; + padding: 5px 10px; + text-align: center; + border-radius: 8px; +} +a.btn { + text-decoration: none; + color: inherit; +} + +.menu-popup { + width: auto; + border: 2px solid #444444; + background: #FFFFFF; + position: absolute; + margin: 2px 0 0; + display: none; + z-index: 10000; +} + +.menu-popup li a { + display: block; + padding: 2px; +} + +.menu-popup li a:hover { + color: #FFFFFF; + background: #3465A4; + text-decoration: none; +} +ul.menu-popup li.divider { + height: 1px; + margin: 3px 0; + overflow: hidden; + background-color: #2d2d2d; +} /* nav */ @@ -1952,6 +1997,20 @@ input#dfrn-url { margin-left: 15px; } +#contact-edit-status-wrapper { + padding: 10px; + border: 1px solid #aaa; + border-radius: 8px; +} + +#contact-edit-contact-status { + font-weight: bold; +} +#contact-edit-actions { + float: right; + display: inline-block; +} + #contact-edit-wrapper { margin-top: 10px; } @@ -1989,11 +2048,6 @@ input#dfrn-url { margin-top: 5px; } -#contact-edit-drop-link { - float: right; - margin-right: 20px; -} - #contact-edit-nav-end { clear: both; } diff --git a/view/theme/frost/templates/contact_edit.tpl b/view/theme/frost/templates/contact_edit.tpl index 731c5e0d4c..76cfd77924 100644 --- a/view/theme/frost/templates/contact_edit.tpl +++ b/view/theme/frost/templates/contact_edit.tpl @@ -6,50 +6,52 @@ {{$tab_str}} - <div id="contact-edit-drop-link" > - <a href="contacts/{{$contact_id}}/drop" class="icon drophide" id="contact-edit-drop-link" onclick="return confirmDelete();" title="{{$delete}}" {{*onmouseover="imgbright(this);" onmouseout="imgdull(this);"*}}></a> - </div> - - <div id="contact-edit-drop-link-end"></div> - - <div id="contact-edit-nav-wrapper" > <div id="contact-edit-links"> - <ul> - <li><div id="contact-edit-rel">{{$relation_text}}</div></li> - <li><div id="contact-edit-nettype">{{$nettype}}</div></li> - {{if $lost_contact}} - <li><div id="lost-contact-message">{{$lost_contact}}</div></li> - {{/if}} - {{if $insecure}} - <li><div id="insecure-message">{{$insecure}}</div></li> - {{/if}} - {{if $blocked}} - <li><div id="block-message">{{$blocked}}</div></li> - {{/if}} - {{if $ignored}} - <li><div id="ignore-message">{{$ignored}}</div></li> - {{/if}} - {{if $archived}} - <li><div id="archive-message">{{$archived}}</div></li> - {{/if}} + <div id="contact-edit-status-wrapper"> + <span id="contact-edit-contact-status">{{$contact_status}}</span> - <li> </li> + <div id="contact-edit-actions"> + <a class="btn" rel="#contact-actions-menu" href="#" id="contact-edit-actions-button">{{$contact_action_button}}</a> - {{if $common_text}} - <li><div id="contact-edit-common"><a href="{{$common_link}}">{{$common_text}}</a></div></li> - {{/if}} - {{if $all_friends}} - <li><div id="contact-edit-allfriends"><a href="allfriends/{{$contact_id}}">{{$all_friends}}</a></div></li> - {{/if}} + <ul role="menu" aria-haspopup="true" id="contact-actions-menu" class="menu-popup" > + {{if $lblsuggest}}<li role="menuitem"><a href="#" title="{{$contact_actions.suggest.title}}" onclick="window.location.href='{{$contact_actions.suggest.url}}'; return false;">{{$contact_actions.suggest.label}}</a></li>{{/if}} + {{if $poll_enabled}}<li role="menuitem"><a href="#" title="{{$contact_actions.update.title}}" onclick="window.location.href='{{$contact_actions.update.url}}'; return false;">{{$contact_actions.update.label}}</a></li>{{/if}} + <li role="menuitem"><a href="#" title="{{$contact_actions.repair.title}}" onclick="window.location.href='{{$contact_actions.repair.url}}'; return false;">{{$contact_actions.repair.label}}</a></li> + <li class="divider"></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.block.title}}" onclick="window.location.href='{{$contact_actions.block.url}}'; return false;">{{$contact_actions.block.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.ignore.title}}" onclick="window.location.href='{{$contact_actions.ignore.url}}'; return false;">{{$contact_actions.ignore.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.archive.title}}" onclick="window.location.href='{{$contact_actions.archive.url}}'; return false;">{{$contact_actions.archive.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.delete.title}}" onclick="return confirmDelete();">{{$contact_actions.delete.label}}</a></li> + </ul> + </div> + <ul> + <li><div id="contact-edit-rel">{{$relation_text}}</div></li> + <li><div id="contact-edit-nettype">{{$nettype}}</div></li> + {{if $poll_enabled}} + <div id="contact-edit-poll-wrapper"> + <div id="contact-edit-last-update-text">{{$lastupdtext}} <span id="contact-edit-last-updated">{{$last_update}}</span></div> + </div> + {{/if}} + {{if $lost_contact}} + <li><div id="lost-contact-message">{{$lost_contact}}</div></li> + {{/if}} + {{if $insecure}} + <li><div id="insecure-message">{{$insecure}}</div></li> + {{/if}} + {{if $blocked}} + <li><div id="block-message">{{$blocked}}</div></li> + {{/if}} + {{if $ignored}} + <li><div id="ignore-message">{{$ignored}}</div></li> + {{/if}} + {{if $archived}} + <li><div id="archive-message">{{$archived}}</div></li> + {{/if}} - <li><a href="network/?cid={{$contact_id}}" id="contact-edit-view-recent">{{$lblrecent}}</a></li> - {{if $lblsuggest}} - <li><a href="fsuggest/{{$contact_id}}" id="contact-edit-suggest">{{$lblsuggest}}</a></li> - {{/if}} - - </ul> + </ul> + </div> </div> </div> <div id="contact-edit-nav-end"></div> @@ -58,12 +60,6 @@ <form action="contacts/{{$contact_id}}" method="post" > <input type="hidden" name="contact_id" value="{{$contact_id}}"> - {{if $poll_enabled}} - <div id="contact-edit-poll-wrapper"> - <div id="contact-edit-last-update-text">{{$lastupdtext}} <span id="contact-edit-last-updated">{{$last_update}}</span></div> - <span id="contact-edit-poll-text">{{$updpub}}</span> {{$poll_interval}} <span id="contact-edit-update-now" class="button"><a href="contacts/{{$contact_id}}/update" >{{$udnow}}</a></span> - </div> - {{/if}} <div id="contact-edit-end" ></div> {{include file="field_checkbox.tpl" field=$hidden}} diff --git a/view/theme/quattro/dark/style.css b/view/theme/quattro/dark/style.css index 847017ee5b..aed53fdac6 100644 --- a/view/theme/quattro/dark/style.css +++ b/view/theme/quattro/dark/style.css @@ -463,7 +463,7 @@ a:hover { text-decoration: underline; } blockquote { - background: #FFFFFF; + background: #ffffff; padding: 1em; margin-left: 1em; border-left: 1em solid #e6e6e6; @@ -1655,6 +1655,9 @@ span[id^="showmore-wrap"] { overflow: hidden; text-overflow: ellipsis; } +#contact-edit-status-wrapper { + border-color: #364e59; +} /* editor */ .jothidden { display: none; diff --git a/view/theme/quattro/green/style.css b/view/theme/quattro/green/style.css index 4cfcb59273..74ab5b9cd0 100644 --- a/view/theme/quattro/green/style.css +++ b/view/theme/quattro/green/style.css @@ -463,7 +463,7 @@ a:hover { text-decoration: underline; } blockquote { - background: #FFFFFF; + background: #ffffff; padding: 1em; margin-left: 1em; border-left: 1em solid #e6e6e6; @@ -1655,6 +1655,9 @@ span[id^="showmore-wrap"] { overflow: hidden; text-overflow: ellipsis; } +#contact-edit-status-wrapper { + border-color: #9ade00; +} /* editor */ .jothidden { display: none; diff --git a/view/theme/quattro/lilac/style.css b/view/theme/quattro/lilac/style.css index 2ff7cfcb0c..327309fa5e 100644 --- a/view/theme/quattro/lilac/style.css +++ b/view/theme/quattro/lilac/style.css @@ -420,7 +420,7 @@ body { font-family: Liberation Sans, helvetica, arial, clean, sans-serif; font-size: 11px; - background-color: #F6ECF9; + background-color: #f6ecf9; color: #2d2d2d; margin: 50px 0 0 0; display: table; @@ -463,7 +463,7 @@ a:hover { text-decoration: underline; } blockquote { - background: #FFFFFF; + background: #ffffff; padding: 1em; margin-left: 1em; border-left: 1em solid #e6e6e6; @@ -1655,6 +1655,9 @@ span[id^="showmore-wrap"] { overflow: hidden; text-overflow: ellipsis; } +#contact-edit-status-wrapper { + border-color: #86608e; +} /* editor */ .jothidden { display: none; @@ -1753,7 +1756,7 @@ span[id^="showmore-wrap"] { height: 20px; width: 500px; font-weight: bold; - border: 1px solid #F6ECF9; + border: 1px solid #f6ecf9; } #jot #jot-title:-webkit-input-placeholder { font-weight: normal; @@ -1780,7 +1783,7 @@ span[id^="showmore-wrap"] { margin: 0; height: 20px; width: 200px; - border: 1px solid #F6ECF9; + border: 1px solid #f6ecf9; } #jot #jot-category:hover { border: 1px solid #999999; diff --git a/view/theme/quattro/quattro.less b/view/theme/quattro/quattro.less index 681cfcc374..d81aedf41a 100644 --- a/view/theme/quattro/quattro.less +++ b/view/theme/quattro/quattro.less @@ -408,19 +408,19 @@ aside { .group-delete-wrapper { float: right; margin-right: 50px; - .drophide { - background-image: url('../../../images/icons/22/delete.png'); - display: block; width: 22px; height: 22px; - opacity: 0.3; - position: relative; - top: -50px; - } - .drop { - background-image: url('../../../images/icons/22/delete.png'); - display: block; width: 22px; height: 22px; - position: relative; - top: -50px; - } + .drophide { + background-image: url('../../../images/icons/22/delete.png'); + display: block; width: 22px; height: 22px; + opacity: 0.3; + position: relative; + top: -50px; + } + .drop { + background-image: url('../../../images/icons/22/delete.png'); + display: block; width: 22px; height: 22px; + position: relative; + top: -50px; + } } /* #group-members { @@ -502,7 +502,7 @@ section { } .sparkle { - cursor: url('icons/lock.cur'), pointer; + cursor: url('icons/lock.cur'), pointer; } /* wall item */ @@ -959,6 +959,7 @@ span[id^="showmore-wrap"] { text-overflow: ellipsis; } +#contact-edit-status-wrapper { border-color: @JotToolsOverBackgroundColor;} /* editor */ .jothidden { display: none; } #jot { diff --git a/view/theme/smoothly/style.css b/view/theme/smoothly/style.css index b9f094932d..87c7342c9d 100644 --- a/view/theme/smoothly/style.css +++ b/view/theme/smoothly/style.css @@ -236,6 +236,39 @@ section { color: #efefef; } +ul.menu-popup { + position: absolute; + display: none; + width: auto; + margin: 2px 0 0; + padding: 0px; + list-style: none; + z-index: 100000; + color: #2e3436; + border-top: 1px; + background: #eeeeee; + border: 1px solid #7C7D7B; + border-radius: 0px 0px 5px 5px; + -webkit-border-radius: 0px 0px 5px 5px; + -moz-border-radius: 0px 0px 5px 5px; + box-shadow: 5px 5px 10px #242424; + -moz-box-shadow: 5px 5px 10px #242424; + -webkit-box-shadow: 5px 5px 10px #242424; +} +ul.menu-popup li a { + white-space: nowrap; + display: block; + padding: 5px 2px; + color: #2e3436; +} +ul.menu-popup li a:hover { + color: #efefef; + background: -webkit-gradient( linear, left top, left bottom, color-stop(0.05, #1873a2), color-stop(1, #6da6c4) ); + background: -moz-linear-gradient( center top, #1873a2 5%, #6da6c4 100% ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#1873a2', endColorstr='#6da6c4'); + background-color: #1873a2; +} + /* ========= */ /* = Login = */ /* ========= */ @@ -4271,16 +4304,6 @@ a.active { .nav-notify.show { display: block; } -ul.menu-popup { - position: absolute; - display: none; - width: 10em; - margin: 0px; - padding: 0px; - list-style: none; - z-index: 100000; - top: 40px; -} #nav-notifications-menu { width: 320px; max-height: 400px; @@ -4298,6 +4321,7 @@ ul.menu-popup { box-shadow: 5px 5px 10px #242424; -moz-box-shadow: 5px 5px 10px #242424; -webkit-box-shadow: 5px 5px 10px #242424; + top: 40px; } #nav-notifications-menu .contactname { @@ -4406,6 +4430,10 @@ ul.menu-popup { background: #000000; } +.notify-seen a { + color: #efefef !important; +} + /* Pages profile widget ----------------------------------------------------------- */ #page-profile, diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index 2b78d25d7f..5b084567b6 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -24,72 +24,72 @@ img { } #pending-update { - float:right; - color: #ffffff; - font-weight: bold; - background-color: #FF0000; - padding: 0em 0.3em; + float:right; + color: #ffffff; + font-weight: bold; + background-color: #FF0000; + padding: 0em 0.3em; } .admin.linklist { - border: 0px; - padding: 0px; - list-style: none; - margin-top: 0px; + border: 0px; + padding: 0px; + list-style: none; + margin-top: 0px; } .admin.link { - list-style-position: inside; - font-size: 1em; -/* padding-left: 5px; - margin: 5px; */ + list-style-position: inside; + font-size: 1em; +/* padding-left: 5px; + margin: 5px; */ } #adminpage dl { - clear: left; - margin-bottom: 2px; - padding-bottom: 2px; - border-bottom: 1px solid black; + clear: left; + margin-bottom: 2px; + padding-bottom: 2px; + border-bottom: 1px solid black; } #adminpage dt { - width: 200px; - float: left; - font-weight: bold; + width: 200px; + float: left; + font-weight: bold; } #adminpage dd { - margin-left: 200px; + margin-left: 200px; } #adminpage h3 { - border-bottom: 1px solid #898989; - margin-bottom: 5px; - margin-top: 10px; + border-bottom: 1px solid #898989; + margin-bottom: 5px; + margin-top: 10px; } #adminpage .submit { - clear:left; + clear:left; } #adminpage #pluginslist { - margin: 0px; padding: 0px; + margin: 0px; padding: 0px; } #adminpage .plugin { - list-style: none; - display: block; - /* border: 1px solid #888888; */ - padding: 1em; - margin-bottom: 5px; - clear: left; + list-style: none; + display: block; + /* border: 1px solid #888888; */ + padding: 1em; + margin-bottom: 5px; + clear: left; } #adminpage .toggleplugin { - float:left; - margin-right: 1em; + float:left; + margin-right: 1em; } -#adminpage table {width:100%; border-bottom: 1p solid #000000; margin: 5px 0px;} +#adminpage table {width:100%; border-bottom: 1px solid #000000; margin: 5px 0px;} #adminpage table th { text-align: left;} #adminpage td .icon { float: left;} #adminpage table#users img { width: 16px; height: 16px; } @@ -247,15 +247,6 @@ div.pager { float: left; } -#contact-edit-drop-link-end { - /* clear: both; */ -} - -#contact-edit-links ul { - list-style: none; - list-style-type: none; -} - .hide-comments-outer { margin-left: 80px; margin-bottom: 5px; @@ -385,6 +376,14 @@ code { overflow: auto; padding: 0px; } +.menu-popup .divider { + width: 90%; + height: 1px; + margin: 3px auto; + overflow: hidden; + background-color: #737373; + opacity: 0.4; +} #saved-search-ul .tool:hover, #nets-sidebar .tool:hover, #sidebar-group-list .tool:hover { @@ -793,7 +792,8 @@ nav #nav-user-linklabel:hover #nav-user-menu, nav #nav-user-linkmenu:hover #nav-user-menu, nav #nav-apps-link:hover #nav-apps-menu, nav #nav-site-linkmenu:hover #nav-site-menu, -nav #nav-notifications-linkmenu:hover #nav-notifications-menu { +nav #nav-notifications-linkmenu:hover #nav-notifications-menu, +#contact-edit-actions:hover #contact-actions-menu { display:block; visibility:visible; opacity:1; @@ -2931,6 +2931,48 @@ a.mail-list-link { color: #999999; } +/* contact edit page */ +#contact-edit-nav-wrapper { + margin-top: 24px; +} +#contact-edit-status-wrapper { + border-color: #c9d8f6; + background-color: #e0e8fa; + border-radius: 3px; +} + +#contact-edit-contact-status { + font-weight: bold; +} + +#contact-edit-drop-link-end { + /* clear: both; */ +} + +#contact-edit-links ul { + list-style: none; + list-style-type: none; +} + +#contact-edit-settings { + margin-top: 10px; +} + +a.btn#contact-edit-actions-button { + cursor: pointer; + border-radius: 3px; + font-size: inherit; + font-weight: normal; + height: auto; + line-height: inherit; + padding: 5px 10px; +} + +#lost-contact-message, #insecure-message, +#block-message, #ignore-message, #archive-message { + color: #CB4437; +} + /* photo album page */ .photo-top-image-wrapper { position: relative; diff --git a/view/theme/vier/templates/contact_edit.tpl b/view/theme/vier/templates/contact_edit.tpl new file mode 100644 index 0000000000..df0053fb68 --- /dev/null +++ b/view/theme/vier/templates/contact_edit.tpl @@ -0,0 +1,99 @@ + +{{if $header}}<h2>{{$header}}</h2>{{/if}} + +<div id="contact-edit-wrapper" > + + {{* Insert Tab-Nav *}} + {{$tab_str}} + + + <div id="contact-edit-nav-wrapper" > + <div id="contact-edit-links"> + <div id="contact-edit-status-wrapper"> + <span id="contact-edit-contact-status">{{$contact_status}}</span> + + {{* This is the Action menu where contact related actions like 'ignore', 'hide' can be performed *}} + <div id="contact-edit-actions"> + <a class="btn" id="contact-edit-actions-button">{{$contact_action_button}}</a> + + <ul role="menu" aria-haspopup="true" id="contact-actions-menu" class="menu-popup" > + {{if $lblsuggest}}<li role="menuitem"><a href="#" title="{{$contact_actions.suggest.title}}" onclick="window.location.href='{{$contact_actions.suggest.url}}'; return false;">{{$contact_actions.suggest.label}}</a></li>{{/if}} + {{if $poll_enabled}}<li role="menuitem"><a href="#" title="{{$contact_actions.update.title}}" onclick="window.location.href='{{$contact_actions.update.url}}'; return false;">{{$contact_actions.update.label}}</a></li>{{/if}} + <li role="menuitem"><a href="#" title="{{$contact_actions.repair.title}}" onclick="window.location.href='{{$contact_actions.repair.url}}'; return false;">{{$contact_actions.repair.label}}</a></li> + <li class="divider"></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.block.title}}" onclick="window.location.href='{{$contact_actions.block.url}}'; return false;">{{$contact_actions.block.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.ignore.title}}" onclick="window.location.href='{{$contact_actions.ignore.url}}'; return false;">{{$contact_actions.ignore.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.archive.title}}" onclick="window.location.href='{{$contact_actions.archive.url}}'; return false;">{{$contact_actions.archive.label}}</a></li> + <li role="menuitem"><a href="#" title="{{$contact_actions.delete.title}}" onclick="return confirmDelete();">{{$contact_actions.delete.label}}</a></li> + </ul> + </div> + + {{* Block with status information about the contact *}} + <ul> + {{if $relation_text}}<li><div id="contact-edit-rel">{{$relation_text}}</div></li>{{/if}} + + {{if $poll_enabled}} + <li><div id="contact-edit-last-update-text">{{$lastupdtext}} <span id="contact-edit-last-updated">{{$last_update}}</span></div> + {{if $poll_interval}} + <span id="contact-edit-poll-text">{{$updpub}}</span> {{$poll_interval}} + {{/if}} + </li> + {{/if}} + + {{if $lost_contact}}<li><div id="lost-contact-message">{{$lost_contact}}</div></li>{{/if}} + {{if $insecure}}<li><div id="insecure-message">{{$insecure}}</div></li> {{/if}} + {{if $blocked}}<li><div id="block-message">{{$blocked}}</div></li>{{/if}} + {{if $ignored}}<li><div id="ignore-message">{{$ignored}}</div></li>{{/if}} + {{if $archived}}<li><div id="archive-message">{{$archived}}</div></li>{{/if}} + </ul> + + <ul> + <!-- <li><a href="network/0?nets=all&cid={{$contact_id}}" id="contact-edit-view-recent">{{$lblrecent}}</a></li> --> + {{if $follow}}<li><div id="contact-edit-follow"><a href="{{$follow}}">{{$follow_text}}</a></div></li>{{/if}} + </ul> + </div> {{* End of contact-edit-status-wrapper *}} + + {{* Some information about the contact from the profile *}} + <dl><dt>{{$profileurllabel}}</dt><dd><a target="blank" href="{{$url}}">{{$profileurl}}</a></dd></dl> + {{if $location}}<dl><dt>{{$location_label}}</dt><dd>{{$location}}</dd></dl>{{/if}} + {{if $keywords}}<dl><dt>{{$keywords_label}}</dt><dd>{{$keywords}}</dd></dl>{{/if}} + {{if $about}}<dl><dt>{{$about_label}}</dt><dd>{{$about}}</dd></dl>{{/if}} + </div>{{* End of contact-edit-links *}} + + <div id="contact-edit-links-end"></div> + + <hr /> + + <h4 id="contact-edit-settings-label" class="fakelink" onclick="openClose('contact-edit-settings')">{{$contact_settings_label}}</h4> + <div id="contact-edit-settings"> + <form action="contacts/{{$contact_id}}" method="post" > + <input type="hidden" name="contact_id" value="{{$contact_id}}"> + + <div id="contact-edit-end" ></div> + {{include file="field_checkbox.tpl" field=$notify}} + {{if $fetch_further_information}} + {{include file="field_select.tpl" field=$fetch_further_information}} + {{if $fetch_further_information.2 == 2 }} {{include file="field_textarea.tpl" field=$ffi_keyword_blacklist}} {{/if}} + {{/if}} + {{include file="field_checkbox.tpl" field=$hidden}} + + <div id="contact-edit-info-wrapper"> + <h4>{{$lbl_info1}}</h4> + <textarea id="contact-edit-info" rows="8" cols="60" name="info">{{$info}}</textarea> + <input class="contact-edit-submit" type="submit" name="submit" value="{{$submit|escape:'html'}}" /> + </div> + <div id="contact-edit-info-end"></div> + + {{if $profile_select}} + <div id="contact-edit-profile-select-text"> + <h4>{{$lbl_vis1}}</h4> + <p>{{$lbl_vis2}}</p> + </div> + {{$profile_select}} + <div id="contact-edit-profile-select-end"></div> + <input class="contact-edit-submit" type="submit" name="submit" value="{{$submit|escape:'html'}}" /> + {{/if}} + </form> + </div> + </div>{{* End of contact-edit-nav-wrapper *}} +</div> From 2e04a00d3044f600b29118e9a309c1192e62ead1 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Mon, 8 Feb 2016 23:25:48 +0100 Subject: [PATCH 069/273] contactedit-actions-button: some adjustment for vier dark scheme --- view/theme/vier/dark.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/view/theme/vier/dark.css b/view/theme/vier/dark.css index 023e419464..99850f2826 100644 --- a/view/theme/vier/dark.css +++ b/view/theme/vier/dark.css @@ -8,6 +8,12 @@ hr { background-color: #343434 !important; } a, .wall-item-name, .fakelink { color: #989898 !important; } +.btn, .btn:hover{ + color: #989898; + border: 2px solid #0C1116; + background-color: #0C1116; + text-shadow: none; +} nav { color: #989898 !important; @@ -36,7 +42,7 @@ body, section, blockquote, blockquote.shared_content, #profile-jot-form, } #profile-jot-acl-wrapper, #event-notice, #event-wrapper, -#cboxLoadedContent, .contact-photo-menu { +#cboxLoadedContent, .contact-photo-menu, #contact-edit-status-wrapper { background-color: #252C33 !important; } From c1c21ada0a9a728f18108168e0a324f7d84bd31c Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Mon, 8 Feb 2016 23:51:51 +0100 Subject: [PATCH 070/273] contactedit-actions-button: remove contact tabs which aren't needed anymore --- doc/Accesskeys.md | 4 ---- mod/contacts.php | 47 +++++++++++++++-------------------------------- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/doc/Accesskeys.md b/doc/Accesskeys.md index c49e79c0ab..57de221c83 100644 --- a/doc/Accesskeys.md +++ b/doc/Accesskeys.md @@ -37,10 +37,6 @@ General * o: Profile * t: Contacts * d: Common friends -* b: Toggle Blocked status -* i: Toggle Ignored status -* v: Toggle Archive status -* r: Repair /message -------- diff --git a/mod/contacts.php b/mod/contacts.php index a2287382a4..f5289cf03a 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -824,7 +824,17 @@ function contacts_content(&$a) { } } -if(! function_exists('contacts_tab')) { +/** + * @brief List of pages for the Contact TabBar + * + * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends' + * + * @param app $a + * @param int $contact_id The ID of the contact + * @param int $active_tab 1 if tab should be marked as active + * + * @return array with with contact TabBar data + */ function contacts_tab($a, $contact_id, $active_tab) { // tabs $tabs = array( @@ -846,6 +856,7 @@ function contacts_tab($a, $contact_id, $active_tab) { ) ); + // Show this tab only if there is visible friend list $x = count_all_friends(local_user(), $contact_id); if ($x) $tabs[] = array('label'=>t('Contacts'), @@ -855,6 +866,7 @@ function contacts_tab($a, $contact_id, $active_tab) { 'id' => 'allfriends-tab', 'accesskey' => 't'); + // Show this tab only if there is visible common friend list $common = count_common_friends(local_user(),$contact_id); if ($common) $tabs[] = array('label'=>t('Common Friends'), @@ -864,41 +876,11 @@ function contacts_tab($a, $contact_id, $active_tab) { 'id' => 'common-loc-tab', 'accesskey' => 'd'); - $tabs[] = array('label' => t('Repair'), - 'url' => $a->get_baseurl(true) . '/crepair/' . $contact_id, - 'sel' => (($active_tab == 5)?'active':''), - 'title' => t('Advanced Contact Settings'), - 'id' => 'repair-tab', - 'accesskey' => 'r'); - - - $tabs[] = array('label' => (($contact['blocked']) ? t('Unblock') : t('Block') ), - 'url' => $a->get_baseurl(true) . '/contacts/' . $contact_id . '/block', - 'sel' => '', - 'title' => t('Toggle Blocked status'), - 'id' => 'toggle-block-tab', - 'accesskey' => 'b'); - - $tabs[] = array('label' => (($contact['readonly']) ? t('Unignore') : t('Ignore') ), - 'url' => $a->get_baseurl(true) . '/contacts/' . $contact_id . '/ignore', - 'sel' => '', - 'title' => t('Toggle Ignored status'), - 'id' => 'toggle-ignore-tab', - 'accesskey' => 'i'); - - $tabs[] = array('label' => (($contact['archive']) ? t('Unarchive') : t('Archive') ), - 'url' => $a->get_baseurl(true) . '/contacts/' . $contact_id . '/archive', - 'sel' => '', - 'title' => t('Toggle Archive status'), - 'id' => 'toggle-archive-tab', - 'accesskey' => 'v'); - $tab_tpl = get_markup_template('common_tabs.tpl'); $tab_str = replace_macros($tab_tpl, array('$tabs' => $tabs)); return $tab_str; } -} if(! function_exists('contact_posts')) { function contact_posts($a, $contact_id) { @@ -990,13 +972,14 @@ function _contact_detail_for_template($rr){ * This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others * * @param array $contact Data about the Contact - * @return array with actions related actions + * @return array with contact related actions */ function contact_actions($contact) { $poll_enabled = in_array($contact['network'], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_FEED, NETWORK_MAIL, NETWORK_MAIL2)); $contact_action = array(); + // Provide friend suggestion only for Friendica contacts if($contact['network'] === NETWORK_DFRN) { $contact_actions['suggest'] = array( 'label' => t('Suggest friends'), From 7af3dd01d808c99a5d7d02d152fe3399625b41e0 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 9 Feb 2016 06:42:00 +0100 Subject: [PATCH 071/273] Poller: Check the number of used database connections --- include/items.php | 10 ++++++++++ include/poller.php | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/include/items.php b/include/items.php index 798ee56958..90c39a9889 100644 --- a/include/items.php +++ b/include/items.php @@ -509,6 +509,16 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : ''); $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : ''); + + if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); + foreach ($trace AS $func) + $function[] = $func["function"]; + + $function = implode(", ", $function); + logger("Both author-link and owner-link are empty. Called by: ".$function, LOGGER_DEBUG); + } + if ($arr['plink'] == "") { $a = get_app(); $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']); diff --git a/include/poller.php b/include/poller.php index 190f3fb1ad..712f6d5788 100644 --- a/include/poller.php +++ b/include/poller.php @@ -26,6 +26,9 @@ function poller_run(&$argv, &$argc){ unset($db_host, $db_user, $db_pass, $db_data); }; + if (poller_max_connections_reached()) + return; + $load = current_load(); if($load) { $maxsysload = intval(get_config('system','maxloadavg')); @@ -117,6 +120,40 @@ function poller_run(&$argv, &$argc){ } +/** + * @brief Checks if the number of database connections has reached a critical limit. + * + * @return bool Are more than 3/4 of the maximum connections used? + */ +function poller_max_connections_reached() { + $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_connections'"); + if (!$r) + return false; + + $max = intval($r[0]["Value"]); + if ($max == 0) + return false; + + $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'"); + if (!$r) + return false; + + $connected = intval($r[0]["Value"]); + if ($connected == 0) + return false; + + $level = $connected / $max; + + logger("Connection usage: ".$connected."/".$max, LOGGER_DEBUG); + + if ($level < (3/4)) + return false; + + logger("Maximum level (3/4) of connections reached: ".$connected."/".$max); + return true; + +} + /** * @brief fix the queue entry if the worker process died * From 5250fed44797a48dbddf7d2b5025f7a7c1b8da11 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Tue, 9 Feb 2016 09:39:29 +0100 Subject: [PATCH 072/273] Revert "Merge pull request #2319 from stieben/develop" This reverts commit 9330a6994c1b9aee49a482efe32e84ca1a944c9b, reversing changes made to ecfb6ec92460e3cd401789e44cd48a8bc503d762. But it keeps changes to doc/Plugins.md and doc/de/Plugins.md --- doc/themes.md | 16 ++-------------- index.php | 11 +---------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/doc/themes.md b/doc/themes.md index ec3a76ac28..add44c776b 100644 --- a/doc/themes.md +++ b/doc/themes.md @@ -59,19 +59,7 @@ The same rule applies to the JavaScript files found in they will be overwritten by files in - /view/theme/**your-theme-name**/js - -### Modules - -You have the freedom to override core modules found in - - /mod - -They will be overwritten by files in - - /view/theme/**your-theme-name**/mod - -Be aware that you can break things easily here if you don't know what you do. Also notice that you can override parts of the module – functions not defined in your theme module will be loaded from the core module. + /view/theme/**your-theme-name**/js. ## Expand an existing Theme @@ -300,4 +288,4 @@ The default file is in /view/default.php if you want to change it, say adding a 4th column for banners of your favourite FLOSS projects, place a new default.php file in your theme directory. -As with the theme.php file, you can use the properties of the $a variable with holds the friendica application to decide what content is displayed. +As with the theme.php file, you can use the properties of the $a variable with holds the friendica application to decide what content is displayed. \ No newline at end of file diff --git a/index.php b/index.php index 2b1053cc1b..bf926d1fe7 100644 --- a/index.php +++ b/index.php @@ -233,16 +233,7 @@ if(strlen($a->module)) { } /** - * If not, next look for module overrides by the theme - */ - - if((! $a->module_loaded) && (file_exists("view/theme/" . current_theme() . "/mod/{$a->module}.php"))) { - include_once("view/theme/" . current_theme() . "/mod/{$a->module}.php"); - // We will not set module_loaded to true to allow for partial overrides. - } - - /** - * Finally, look for a 'standard' program module in the 'mod' directory + * If not, next look for a 'standard' program module in the 'mod' directory */ if((! $a->module_loaded) && (file_exists("mod/{$a->module}.php"))) { From 7b2fadcf4330b47ffedddf6892b94fd0dd4ae86f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 9 Feb 2016 10:21:10 +0100 Subject: [PATCH 073/273] Get rid of the "guid" table. We don't need it anymore. --- doc/database.md | 1 - doc/database/db_guid.md | 12 ------------ include/dbstructure.php | 15 --------------- include/items.php | 13 ------------- mod/item.php | 3 --- 5 files changed, 44 deletions(-) delete mode 100644 doc/database/db_guid.md diff --git a/doc/database.md b/doc/database.md index a0b28f4e84..e37df05e09 100644 --- a/doc/database.md +++ b/doc/database.md @@ -27,7 +27,6 @@ Database Tables | [group](help/database/db_group) | privacy groups, group info | | [group_member](help/database/db_group_member) | privacy groups, member info | | [gserver](help/database/db_gserver) | | -| [guid](help/database/db_guid) | | | [hook](help/database/db_hook) | plugin hook registry | | [intro](help/database/db_intro) | | | [item](help/database/db_item) | all posts | diff --git a/doc/database/db_guid.md b/doc/database/db_guid.md deleted file mode 100644 index f607597a21..0000000000 --- a/doc/database/db_guid.md +++ /dev/null @@ -1,12 +0,0 @@ -Table guid -========== - -| Field | Description | Type | Null | Key | Default | Extra | -|---------|------------------|------------------|------|-----|---------|----------------| -| id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment | -| guid | | varchar(255) | NO | MUL | | | -| plink | | varchar(255) | NO | MUL | | | -| uri | | varchar(255) | NO | MUL | | | -| network | | varchar(32) | NO | | | | - -Return to [database documentation](help/database) diff --git a/include/dbstructure.php b/include/dbstructure.php index 96d18cd789..ddf036f2c1 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -748,21 +748,6 @@ function db_definition() { "nurl" => array("nurl"), ) ); - $database["guid"] = array( - "fields" => array( - "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), - "guid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "plink" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "network" => array("type" => "varchar(32)", "not null" => "1", "default" => ""), - ), - "indexes" => array( - "PRIMARY" => array("id"), - "guid" => array("guid"), - "plink" => array("plink"), - "uri" => array("uri"), - ) - ); $database["hook"] = array( "fields" => array( "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), diff --git a/include/items.php b/include/items.php index 90c39a9889..1af6fe1b52 100644 --- a/include/items.php +++ b/include/items.php @@ -291,16 +291,6 @@ function add_page_info_to_body($body, $texturl = false, $no_photos = false) { return $body; } -function add_guid($item) { - $r = q("SELECT `guid` FROM `guid` WHERE `guid` = '%s' LIMIT 1", dbesc($item["guid"])); - if ($r) - return; - - q("INSERT INTO `guid` (`guid`,`plink`,`uri`,`network`) VALUES ('%s','%s','%s','%s')", - dbesc($item["guid"]), dbesc($item["plink"]), - dbesc($item["uri"]), dbesc($item["network"])); -} - /** * Adds a "lang" specification in a "postopts" element of given $arr, * if possible and not already present. @@ -778,9 +768,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa return 0; } elseif(count($r)) { - // Store the guid and other relevant data - add_guid($arr); - $current_post = $r[0]['id']; logger('item_store: created item ' . $current_post); diff --git a/mod/item.php b/mod/item.php index 8c5a479646..7e575a17e4 100644 --- a/mod/item.php +++ b/mod/item.php @@ -844,9 +844,6 @@ function item_post(&$a) { // NOTREACHED } - // Store the guid and other relevant data - add_guid($datarray); - $post_id = $r[0]['id']; logger('mod_item: saved item ' . $post_id); From c28109ca947b5c5867ec052058dd2115f82aa0ff Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Tue, 9 Feb 2016 11:06:17 +0100 Subject: [PATCH 074/273] Update HTMLPurifier to v4.7.0 --- .../HTMLPurifier/AttrDef/CSS/AlphaValue.php | 21 - .../AttrDef/CSS/DenyElementDecorator.php | 28 - .../HTMLPurifier/AttrDef/CSS/FontFamily.php | 72 - library/HTMLPurifier/AttrDef/CSS/Length.php | 47 - .../HTMLPurifier/AttrDef/CSS/ListStyle.php | 78 - .../HTMLPurifier/AttrDef/CSS/Percentage.php | 40 - library/HTMLPurifier/AttrDef/HTML/Bool.php | 28 - library/HTMLPurifier/AttrDef/HTML/Color.php | 32 - .../HTMLPurifier/AttrDef/HTML/FrameTarget.php | 21 - library/HTMLPurifier/AttrDef/HTML/ID.php | 70 - library/HTMLPurifier/AttrDef/HTML/Length.php | 41 - .../HTMLPurifier/AttrDef/HTML/MultiLength.php | 41 - library/HTMLPurifier/AttrDef/HTML/Pixels.php | 48 - library/HTMLPurifier/AttrDef/Text.php | 15 - library/HTMLPurifier/AttrDef/URI/Host.php | 62 - library/HTMLPurifier/AttrDef/URI/IPv6.php | 99 - .../HTMLPurifier/AttrTransform/BoolToCSS.php | 36 - .../HTMLPurifier/AttrTransform/EnumToCSS.php | 58 - library/HTMLPurifier/AttrTransform/Length.php | 27 - library/HTMLPurifier/AttrTransform/Name.php | 21 - .../HTMLPurifier/AttrTransform/NameSync.php | 27 - .../HTMLPurifier/AttrTransform/SafeObject.php | 16 - .../HTMLPurifier/AttrTransform/Textarea.php | 18 - library/HTMLPurifier/Bootstrap.php | 98 - library/HTMLPurifier/CSSDefinition.php | 292 - library/HTMLPurifier/ChildDef/Optional.php | 26 - library/HTMLPurifier/ChildDef/Required.php | 117 - .../ChildDef/StrictBlockquote.php | 88 - library/HTMLPurifier/ChildDef/Table.php | 142 - library/HTMLPurifier/Config.php | 580 -- .../ConfigSchema/ValidatorAtom.php | 66 - library/HTMLPurifier/ConfigSchema/schema.ser | Bin 13244 -> 0 bytes .../schema/HTML.AllowedElements.txt | 18 - library/HTMLPurifier/Context.php | 82 - library/HTMLPurifier/Definition.php | 39 - .../DefinitionCache/Decorator.php | 62 - .../DefinitionCache/Decorator/Cleanup.php | 43 - .../DefinitionCache/Decorator/Memory.php | 46 - .../DefinitionCache/Decorator/Template.php.in | 47 - library/HTMLPurifier/DefinitionCache/Null.php | 39 - .../DefinitionCache/Serializer.php | 172 - .../HTMLPurifier/EntityLookup/entities.ser | 1 - .../Filter/ExtractStyleBlocks.php | 135 - library/HTMLPurifier/Filter/YouTube.php | 39 - library/HTMLPurifier/HTMLModule/Forms.php | 118 - .../HTMLPurifier/HTMLModule/Tidy/Strict.php | 21 - library/HTMLPurifier/Injector/RemoveEmpty.php | 51 - library/HTMLPurifier/Language/messages/en.php | 63 - library/HTMLPurifier/Lexer/PEARSax3.php | 139 - library/HTMLPurifier/Lexer/PH5P.php | 3906 -------------- library/HTMLPurifier/Strategy/FixNesting.php | 328 -- library/HTMLPurifier/Token.php | 57 - library/HTMLPurifier/Token/Comment.php | 22 - library/HTMLPurifier/TokenFactory.php | 94 - library/HTMLPurifier/URI.php | 173 - library/HTMLPurifier/URIFilter.php | 45 - .../URIFilter/DisableExternal.php | 23 - .../URIFilter/DisableExternalResources.php | 12 - .../HTMLPurifier/URIFilter/HostBlacklist.php | 21 - library/HTMLPurifier/URIFilter/Munge.php | 58 - library/HTMLPurifier/URIScheme.php | 42 - library/HTMLPurifier/URIScheme/news.php | 22 - library/HTMLPurifier/URIScheme/nntp.php | 20 - library/HTMLPurifier/VarParser.php | 154 - library/ezyang/htmlpurifier/CREDITS | 9 + library/ezyang/htmlpurifier/INSTALL | 374 ++ library/ezyang/htmlpurifier/INSTALL.fr.utf8 | 60 + library/ezyang/htmlpurifier/LICENSE | 504 ++ library/ezyang/htmlpurifier/NEWS | 1094 ++++ library/ezyang/htmlpurifier/README | 24 + library/ezyang/htmlpurifier/TODO | 150 + library/ezyang/htmlpurifier/VERSION | 1 + library/ezyang/htmlpurifier/WHATSNEW | 4 + library/ezyang/htmlpurifier/WYSIWYG | 20 + library/ezyang/htmlpurifier/composer.json | 22 + .../extras/ConfigDoc/HTMLXSLTProcessor.php | 91 + .../ezyang/htmlpurifier/extras/FSTools.php | 164 + .../htmlpurifier/extras/FSTools/File.php | 141 + .../extras/HTMLPurifierExtras.auto.php | 11 + .../extras/HTMLPurifierExtras.autoload.php | 26 + .../extras/HTMLPurifierExtras.php | 31 + library/ezyang/htmlpurifier/extras/README | 32 + .../library}/HTMLPurifier.auto.php | 0 .../library}/HTMLPurifier.autoload.php | 8 +- .../library/HTMLPurifier.composer.php | 4 + .../library}/HTMLPurifier.func.php | 8 +- .../library}/HTMLPurifier.includes.php | 21 +- .../library}/HTMLPurifier.kses.php | 4 +- .../library}/HTMLPurifier.path.php | 0 .../htmlpurifier/library}/HTMLPurifier.php | 153 +- .../library}/HTMLPurifier.safe-includes.php | 19 + .../library/HTMLPurifier/Arborize.php | 71 + .../library}/HTMLPurifier/AttrCollections.php | 53 +- .../library}/HTMLPurifier/AttrDef.php | 53 +- .../library}/HTMLPurifier/AttrDef/CSS.php | 39 +- .../HTMLPurifier/AttrDef/CSS/AlphaValue.php | 34 + .../HTMLPurifier/AttrDef/CSS/Background.php | 60 +- .../AttrDef/CSS/BackgroundPosition.php | 54 +- .../HTMLPurifier/AttrDef/CSS/Border.php | 21 +- .../HTMLPurifier/AttrDef/CSS/Color.php | 67 +- .../HTMLPurifier/AttrDef/CSS/Composite.php | 22 +- .../AttrDef/CSS/DenyElementDecorator.php | 44 + .../HTMLPurifier/AttrDef/CSS/Filter.php | 49 +- .../HTMLPurifier/AttrDef/CSS/Font.php | 89 +- .../HTMLPurifier/AttrDef/CSS/FontFamily.php | 219 + .../HTMLPurifier/AttrDef/CSS/Ident.php | 32 + .../AttrDef/CSS/ImportantDecorator.php | 28 +- .../HTMLPurifier/AttrDef/CSS/Length.php | 77 + .../HTMLPurifier/AttrDef/CSS/ListStyle.php | 112 + .../HTMLPurifier/AttrDef/CSS/Multiple.php | 33 +- .../HTMLPurifier/AttrDef/CSS/Number.php | 45 +- .../HTMLPurifier/AttrDef/CSS/Percentage.php | 54 + .../AttrDef/CSS/TextDecoration.php | 20 +- .../library}/HTMLPurifier/AttrDef/CSS/URI.php | 38 +- .../library/HTMLPurifier/AttrDef/Clone.php | 44 + .../library}/HTMLPurifier/AttrDef/Enum.php | 28 +- .../HTMLPurifier/AttrDef/HTML/Bool.php | 48 + .../HTMLPurifier/AttrDef/HTML/Class.php | 22 +- .../HTMLPurifier/AttrDef/HTML/Color.php | 51 + .../HTMLPurifier/AttrDef/HTML/FrameTarget.php | 38 + .../library/HTMLPurifier/AttrDef/HTML/ID.php | 105 + .../HTMLPurifier/AttrDef/HTML/Length.php | 56 + .../HTMLPurifier/AttrDef/HTML/LinkTypes.php | 43 +- .../HTMLPurifier/AttrDef/HTML/MultiLength.php | 60 + .../HTMLPurifier/AttrDef/HTML/Nmtokens.php | 40 +- .../HTMLPurifier/AttrDef/HTML/Pixels.php | 76 + .../library}/HTMLPurifier/AttrDef/Integer.php | 56 +- .../library}/HTMLPurifier/AttrDef/Lang.php | 39 +- .../library}/HTMLPurifier/AttrDef/Switch.php | 27 +- .../library/HTMLPurifier/AttrDef/Text.php | 21 + .../library}/HTMLPurifier/AttrDef/URI.php | 72 +- .../HTMLPurifier/AttrDef/URI/Email.php | 5 +- .../AttrDef/URI/Email/SimpleCheck.php | 14 +- .../library/HTMLPurifier/AttrDef/URI/Host.php | 128 + .../HTMLPurifier/AttrDef/URI/IPv4.php | 28 +- .../library/HTMLPurifier/AttrDef/URI/IPv6.php | 89 + .../library}/HTMLPurifier/AttrTransform.php | 28 +- .../HTMLPurifier/AttrTransform/Background.php | 21 +- .../HTMLPurifier/AttrTransform/BdoDir.php | 14 +- .../HTMLPurifier/AttrTransform/BgColor.php | 21 +- .../HTMLPurifier/AttrTransform/BoolToCSS.php | 47 + .../HTMLPurifier/AttrTransform/Border.php | 18 +- .../HTMLPurifier/AttrTransform/EnumToCSS.php | 68 + .../AttrTransform/ImgRequired.php | 19 +- .../HTMLPurifier/AttrTransform/ImgSpace.php | 39 +- .../HTMLPurifier/AttrTransform/Input.php | 34 +- .../HTMLPurifier/AttrTransform/Lang.php | 15 +- .../HTMLPurifier/AttrTransform/Length.php | 45 + .../HTMLPurifier/AttrTransform/Name.php | 33 + .../HTMLPurifier/AttrTransform/NameSync.php | 41 + .../HTMLPurifier/AttrTransform/Nofollow.php | 52 + .../HTMLPurifier/AttrTransform/SafeEmbed.php | 12 +- .../HTMLPurifier/AttrTransform/SafeObject.php | 28 + .../HTMLPurifier/AttrTransform/SafeParam.php | 29 +- .../AttrTransform/ScriptRequired.php | 9 +- .../AttrTransform/TargetBlank.php | 45 + .../HTMLPurifier/AttrTransform/Textarea.php | 27 + .../library}/HTMLPurifier/AttrTypes.php | 45 +- .../library}/HTMLPurifier/AttrValidator.php | 66 +- .../library/HTMLPurifier/Bootstrap.php | 124 + .../library/HTMLPurifier/CSSDefinition.php | 474 ++ .../library}/HTMLPurifier/ChildDef.php | 26 +- .../HTMLPurifier/ChildDef/Chameleon.php | 33 +- .../library}/HTMLPurifier/ChildDef/Custom.php | 60 +- .../library}/HTMLPurifier/ChildDef/Empty.php | 22 +- .../library/HTMLPurifier/ChildDef/List.php | 86 + .../HTMLPurifier/ChildDef/Optional.php | 45 + .../HTMLPurifier/ChildDef/Required.php | 118 + .../ChildDef/StrictBlockquote.php | 110 + .../library/HTMLPurifier/ChildDef/Table.php | 224 + .../library/HTMLPurifier/Config.php | 920 ++++ .../library}/HTMLPurifier/ConfigSchema.php | 78 +- .../ConfigSchema/Builder/ConfigSchema.php | 8 +- .../HTMLPurifier/ConfigSchema/Builder/Xml.php | 94 +- .../HTMLPurifier/ConfigSchema/Exception.php | 0 .../HTMLPurifier/ConfigSchema/Interchange.php | 11 +- .../ConfigSchema/Interchange/Directive.php | 28 +- .../ConfigSchema/Interchange/Id.php | 33 +- .../ConfigSchema/InterchangeBuilder.php | 90 +- .../HTMLPurifier/ConfigSchema/Validator.php | 90 +- .../ConfigSchema/ValidatorAtom.php | 130 + .../HTMLPurifier/ConfigSchema/schema.ser | Bin 0 -> 15305 bytes .../schema/Attr.AllowedClasses.txt | 0 .../schema/Attr.AllowedFrameTargets.txt | 0 .../ConfigSchema/schema/Attr.AllowedRel.txt | 0 .../ConfigSchema/schema/Attr.AllowedRev.txt | 0 .../schema/Attr.ClassUseCDATA.txt | 0 .../schema/Attr.DefaultImageAlt.txt | 0 .../schema/Attr.DefaultInvalidImage.txt | 0 .../schema/Attr.DefaultInvalidImageAlt.txt | 0 .../schema/Attr.DefaultTextDir.txt | 0 .../ConfigSchema/schema/Attr.EnableID.txt | 0 .../schema/Attr.ForbiddenClasses.txt | 0 .../ConfigSchema/schema/Attr.IDBlacklist.txt | 0 .../schema/Attr.IDBlacklistRegexp.txt | 0 .../ConfigSchema/schema/Attr.IDPrefix.txt | 0 .../schema/Attr.IDPrefixLocal.txt | 0 .../schema/AutoFormat.AutoParagraph.txt | 0 .../ConfigSchema/schema/AutoFormat.Custom.txt | 0 .../schema/AutoFormat.DisplayLinkURI.txt | 0 .../schema/AutoFormat.Linkify.txt | 0 .../AutoFormat.PurifierLinkify.DocURL.txt | 0 .../schema/AutoFormat.PurifierLinkify.txt | 0 .../AutoFormat.RemoveEmpty.Predicate.txt | 14 + ...rmat.RemoveEmpty.RemoveNbsp.Exceptions.txt | 0 .../AutoFormat.RemoveEmpty.RemoveNbsp.txt | 0 .../schema/AutoFormat.RemoveEmpty.txt | 0 ...utoFormat.RemoveSpansWithoutAttributes.txt | 0 .../schema/CSS.AllowImportant.txt | 0 .../ConfigSchema/schema/CSS.AllowTricky.txt | 0 .../ConfigSchema/schema/CSS.AllowedFonts.txt | 12 + .../schema/CSS.AllowedProperties.txt | 0 .../ConfigSchema/schema/CSS.DefinitionRev.txt | 0 .../schema/CSS.ForbiddenProperties.txt | 13 + .../ConfigSchema/schema/CSS.MaxImgLength.txt | 0 .../ConfigSchema/schema/CSS.Proprietary.txt | 0 .../ConfigSchema/schema/CSS.Trusted.txt | 9 + .../schema/Cache.DefinitionImpl.txt | 0 .../schema/Cache.SerializerPath.txt | 0 .../schema/Cache.SerializerPermissions.txt | 11 + .../schema/Core.AggressivelyFixLt.txt | 0 .../schema/Core.AllowHostnameUnderscore.txt | 16 + .../schema/Core.CollectErrors.txt | 0 .../schema/Core.ColorKeywords.txt | 3 +- .../schema/Core.ConvertDocumentToFragment.txt | 0 .../Core.DirectLexLineNumberSyncInterval.txt | 0 .../schema/Core.DisableExcludes.txt | 14 + .../ConfigSchema/schema/Core.EnableIDNA.txt | 9 + .../ConfigSchema/schema/Core.Encoding.txt | 0 .../schema/Core.EscapeInvalidChildren.txt | 6 +- .../schema/Core.EscapeInvalidTags.txt | 0 .../schema/Core.EscapeNonASCIICharacters.txt | 0 .../schema/Core.HiddenElements.txt | 0 .../ConfigSchema/schema/Core.Language.txt | 0 .../ConfigSchema/schema/Core.LexerImpl.txt | 0 .../schema/Core.MaintainLineNumbers.txt | 0 .../schema/Core.NormalizeNewlines.txt | 11 + .../schema/Core.RemoveInvalidImg.txt | 0 .../Core.RemoveProcessingInstructions.txt | 11 + .../schema/Core.RemoveScriptContents.txt | 0 .../ConfigSchema/schema/Filter.Custom.txt | 0 .../Filter.ExtractStyleBlocks.Escaping.txt | 0 .../Filter.ExtractStyleBlocks.Scope.txt | 0 .../Filter.ExtractStyleBlocks.TidyImpl.txt | 0 .../schema/Filter.ExtractStyleBlocks.txt | 0 .../ConfigSchema/schema/Filter.YouTube.txt | 5 + .../ConfigSchema/schema/HTML.Allowed.txt | 11 +- .../schema/HTML.AllowedAttributes.txt | 0 .../schema/HTML.AllowedComments.txt | 10 + .../schema/HTML.AllowedCommentsRegexp.txt | 15 + .../schema/HTML.AllowedElements.txt | 23 + .../schema/HTML.AllowedModules.txt | 0 .../schema/HTML.Attr.Name.UseCDATA.txt | 0 .../ConfigSchema/schema/HTML.BlockWrapper.txt | 0 .../ConfigSchema/schema/HTML.CoreModules.txt | 0 .../schema/HTML.CustomDoctype.txt | 2 +- .../ConfigSchema/schema/HTML.DefinitionID.txt | 0 .../schema/HTML.DefinitionRev.txt | 0 .../ConfigSchema/schema/HTML.Doctype.txt | 0 .../schema/HTML.FlashAllowFullScreen.txt | 11 + .../schema/HTML.ForbiddenAttributes.txt | 0 .../schema/HTML.ForbiddenElements.txt | 0 .../ConfigSchema/schema/HTML.MaxImgLength.txt | 0 .../ConfigSchema/schema/HTML.Nofollow.txt | 7 + .../ConfigSchema/schema/HTML.Parent.txt | 0 .../ConfigSchema/schema/HTML.Proprietary.txt | 0 .../ConfigSchema/schema/HTML.SafeEmbed.txt | 0 .../ConfigSchema/schema/HTML.SafeIframe.txt | 13 + .../ConfigSchema/schema/HTML.SafeObject.txt | 0 .../schema/HTML.SafeScripting.txt | 10 + .../ConfigSchema/schema/HTML.Strict.txt | 0 .../ConfigSchema/schema/HTML.TargetBlank.txt | 8 + .../ConfigSchema/schema/HTML.TidyAdd.txt | 0 .../ConfigSchema/schema/HTML.TidyLevel.txt | 0 .../ConfigSchema/schema/HTML.TidyRemove.txt | 0 .../ConfigSchema/schema/HTML.Trusted.txt | 1 + .../ConfigSchema/schema/HTML.XHTML.txt | 0 .../schema/Output.CommentScriptContents.txt | 0 .../schema/Output.FixInnerHTML.txt | 15 + .../schema/Output.FlashCompat.txt | 0 .../ConfigSchema/schema/Output.Newline.txt | 0 .../ConfigSchema/schema/Output.SortAttr.txt | 0 .../ConfigSchema/schema/Output.TidyFormat.txt | 0 .../ConfigSchema/schema/Test.ForceNoIconv.txt | 0 .../schema/URI.AllowedSchemes.txt | 4 +- .../ConfigSchema/schema/URI.Base.txt | 0 .../ConfigSchema/schema/URI.DefaultScheme.txt | 0 .../ConfigSchema/schema/URI.DefinitionID.txt | 0 .../ConfigSchema/schema/URI.DefinitionRev.txt | 0 .../ConfigSchema/schema/URI.Disable.txt | 0 .../schema/URI.DisableExternal.txt | 0 .../schema/URI.DisableExternalResources.txt | 0 .../schema/URI.DisableResources.txt | 7 +- .../ConfigSchema/schema/URI.Host.txt | 0 .../ConfigSchema/schema/URI.HostBlacklist.txt | 0 .../ConfigSchema/schema/URI.MakeAbsolute.txt | 0 .../ConfigSchema/schema/URI.Munge.txt | 0 .../schema/URI.MungeResources.txt | 0 .../schema/URI.MungeSecretKey.txt | 2 +- .../schema/URI.OverrideAllowedSchemes.txt | 0 .../schema/URI.SafeIframeRegexp.txt | 22 + .../HTMLPurifier/ConfigSchema/schema/info.ini | 0 .../library}/HTMLPurifier/ContentSets.php | 55 +- .../library/HTMLPurifier/Context.php | 95 + .../library/HTMLPurifier/Definition.php | 55 + .../library}/HTMLPurifier/DefinitionCache.php | 57 +- .../DefinitionCache/Decorator.php | 112 + .../DefinitionCache/Decorator/Cleanup.php | 78 + .../DefinitionCache/Decorator/Memory.php | 85 + .../DefinitionCache/Decorator/Template.php.in | 82 + .../HTMLPurifier/DefinitionCache/Null.php | 76 + .../DefinitionCache/Serializer.php | 291 + .../DefinitionCache/Serializer/README | 0 .../HTMLPurifier/DefinitionCacheFactory.php | 47 +- .../library}/HTMLPurifier/Doctype.php | 17 +- .../library}/HTMLPurifier/DoctypeRegistry.php | 87 +- .../library}/HTMLPurifier/ElementDef.php | 85 +- .../library}/HTMLPurifier/Encoder.php | 303 +- .../library}/HTMLPurifier/EntityLookup.php | 16 +- .../HTMLPurifier/EntityLookup/entities.ser | 1 + .../library}/HTMLPurifier/EntityParser.php | 53 +- .../library}/HTMLPurifier/ErrorCollector.php | 87 +- .../library}/HTMLPurifier/ErrorStruct.php | 20 +- .../library}/HTMLPurifier/Exception.php | 0 .../library}/HTMLPurifier/Filter.php | 18 +- .../Filter/ExtractStyleBlocks.php | 338 ++ .../library/HTMLPurifier/Filter/YouTube.php | 65 + .../library}/HTMLPurifier/Generator.php | 164 +- .../library}/HTMLPurifier/HTMLDefinition.php | 211 +- .../library}/HTMLPurifier/HTMLModule.php | 122 +- .../library}/HTMLPurifier/HTMLModule/Bdo.php | 21 +- .../HTMLModule/CommonAttributes.php | 7 +- .../library}/HTMLPurifier/HTMLModule/Edit.php | 25 +- .../library/HTMLPurifier/HTMLModule/Forms.php | 190 + .../HTMLPurifier/HTMLModule/Hypertext.php | 15 +- .../HTMLPurifier/HTMLModule/Iframe.php | 51 + .../HTMLPurifier/HTMLModule/Image.php | 17 +- .../HTMLPurifier/HTMLModule/Legacy.php | 89 +- .../library}/HTMLPurifier/HTMLModule/List.php | 28 +- .../library}/HTMLPurifier/HTMLModule/Name.php | 13 +- .../HTMLPurifier/HTMLModule/Nofollow.php | 25 + .../HTMLModule/NonXMLCommonAttributes.php | 6 + .../HTMLPurifier/HTMLModule/Object.php | 31 +- .../HTMLPurifier/HTMLModule/Presentation.php | 26 +- .../HTMLPurifier/HTMLModule/Proprietary.php | 19 +- .../library}/HTMLPurifier/HTMLModule/Ruby.php | 17 +- .../HTMLPurifier/HTMLModule/SafeEmbed.php | 20 +- .../HTMLPurifier/HTMLModule/SafeObject.php | 33 +- .../HTMLPurifier/HTMLModule/SafeScripting.php | 40 + .../HTMLPurifier/HTMLModule/Scripting.php | 31 +- .../HTMLModule/StyleAttribute.php | 15 +- .../HTMLPurifier/HTMLModule/Tables.php | 29 +- .../HTMLPurifier/HTMLModule/Target.php | 11 +- .../HTMLPurifier/HTMLModule/TargetBlank.php | 24 + .../library}/HTMLPurifier/HTMLModule/Text.php | 56 +- .../library}/HTMLPurifier/HTMLModule/Tidy.php | 85 +- .../HTMLPurifier/HTMLModule/Tidy/Name.php | 15 +- .../HTMLModule/Tidy/Proprietary.php | 14 +- .../HTMLPurifier/HTMLModule/Tidy/Strict.php | 43 + .../HTMLModule/Tidy/Transitional.php | 7 + .../HTMLPurifier/HTMLModule/Tidy/XHTML.php | 15 +- .../HTMLModule/Tidy/XHTMLAndHTML4.php | 146 +- .../HTMLModule/XMLCommonAttributes.php | 6 + .../HTMLPurifier/HTMLModuleManager.php | 166 +- .../library}/HTMLPurifier/IDAccumulator.php | 24 +- .../library}/HTMLPurifier/Injector.php | 194 +- .../HTMLPurifier/Injector/AutoParagraph.php | 99 +- .../HTMLPurifier/Injector/DisplayLinkURI.php | 22 +- .../HTMLPurifier/Injector/Linkify.php | 27 +- .../HTMLPurifier/Injector/PurifierLinkify.php | 46 +- .../HTMLPurifier/Injector/RemoveEmpty.php | 106 + .../Injector/RemoveSpansWithoutAttributes.php | 36 +- .../HTMLPurifier/Injector/SafeObject.php | 53 +- .../library}/HTMLPurifier/Language.php | 97 +- .../Language/classes/en-x-test.php | 3 - .../Language/messages/en-x-test.php | 0 .../Language/messages/en-x-testmini.php | 0 .../HTMLPurifier/Language/messages/en.php | 55 + .../library}/HTMLPurifier/LanguageFactory.php | 81 +- .../library}/HTMLPurifier/Length.php | 91 +- .../library}/HTMLPurifier/Lexer.php | 189 +- .../library}/HTMLPurifier/Lexer/DOMLex.php | 162 +- .../library}/HTMLPurifier/Lexer/DirectLex.php | 217 +- .../library/HTMLPurifier/Lexer/PH5P.php | 4787 +++++++++++++++++ .../library/HTMLPurifier/Node.php | 49 + .../library/HTMLPurifier/Node/Comment.php | 36 + .../library/HTMLPurifier/Node/Element.php | 59 + .../library/HTMLPurifier/Node/Text.php | 54 + .../library}/HTMLPurifier/PercentEncoder.php | 37 +- .../library}/HTMLPurifier/Printer.php | 134 +- .../HTMLPurifier/Printer/CSSDefinition.php | 12 +- .../HTMLPurifier/Printer/ConfigForm.css | 0 .../HTMLPurifier/Printer/ConfigForm.js | 0 .../HTMLPurifier/Printer/ConfigForm.php | 255 +- .../HTMLPurifier/Printer/HTMLDefinition.php | 208 +- .../library}/HTMLPurifier/PropertyList.php | 68 +- .../HTMLPurifier/PropertyListIterator.php | 22 +- .../library/HTMLPurifier/Queue.php | 56 + .../library}/HTMLPurifier/Strategy.php | 8 +- .../HTMLPurifier/Strategy/Composite.php | 13 +- .../library}/HTMLPurifier/Strategy/Core.php | 5 +- .../HTMLPurifier/Strategy/FixNesting.php | 181 + .../HTMLPurifier/Strategy/MakeWellFormed.php | 383 +- .../Strategy/RemoveForeignElements.php | 94 +- .../Strategy/ValidateAttributes.php | 22 +- .../library}/HTMLPurifier/StringHash.php | 16 +- .../HTMLPurifier/StringHashParser.php | 52 +- .../library}/HTMLPurifier/TagTransform.php | 15 +- .../HTMLPurifier/TagTransform/Font.php | 42 +- .../HTMLPurifier/TagTransform/Simple.php | 21 +- .../library/HTMLPurifier/Token.php | 100 + .../library/HTMLPurifier/Token/Comment.php | 38 + .../library}/HTMLPurifier/Token/Empty.php | 6 +- .../library}/HTMLPurifier/Token/End.php | 9 +- .../library}/HTMLPurifier/Token/Start.php | 1 - .../library}/HTMLPurifier/Token/Tag.php | 22 +- .../library}/HTMLPurifier/Token/Text.php | 34 +- .../library/HTMLPurifier/TokenFactory.php | 118 + .../htmlpurifier/library/HTMLPurifier/URI.php | 314 ++ .../library}/HTMLPurifier/URIDefinition.php | 39 +- .../library/HTMLPurifier/URIFilter.php | 74 + .../URIFilter/DisableExternal.php | 54 + .../URIFilter/DisableExternalResources.php | 25 + .../URIFilter/DisableResources.php | 22 + .../HTMLPurifier/URIFilter/HostBlacklist.php | 46 + .../HTMLPurifier/URIFilter/MakeAbsolute.php | 74 +- .../library/HTMLPurifier/URIFilter/Munge.php | 115 + .../HTMLPurifier/URIFilter/SafeIframe.php | 68 + .../library}/HTMLPurifier/URIParser.php | 9 +- .../library/HTMLPurifier/URIScheme.php | 102 + .../library}/HTMLPurifier/URIScheme/data.php | 58 +- .../library/HTMLPurifier/URIScheme/file.php | 44 + .../library}/HTMLPurifier/URIScheme/ftp.php | 29 +- .../library}/HTMLPurifier/URIScheme/http.php | 26 +- .../library}/HTMLPurifier/URIScheme/https.php | 12 +- .../HTMLPurifier/URIScheme/mailto.php | 23 +- .../library/HTMLPurifier/URIScheme/news.php | 35 + .../library/HTMLPurifier/URIScheme/nntp.php | 32 + .../HTMLPurifier/URISchemeRegistry.php | 41 +- .../library}/HTMLPurifier/UnitConverter.php | 117 +- .../library/HTMLPurifier/VarParser.php | 198 + .../HTMLPurifier/VarParser/Flexible.php | 88 +- .../HTMLPurifier/VarParser/Native.php | 18 +- .../HTMLPurifier/VarParserException.php | 0 .../library/HTMLPurifier/Zipper.php | 157 + library/ezyang/htmlpurifier/package.php | 61 + library/ezyang/htmlpurifier/phpdoc.ini | 102 + library/ezyang/htmlpurifier/plugins/modx.txt | 112 + .../htmlpurifier/plugins/phorum/.gitignore | 2 + .../htmlpurifier/plugins/phorum/Changelog | 27 + .../htmlpurifier/plugins/phorum/INSTALL | 84 + .../ezyang/htmlpurifier/plugins/phorum/README | 45 + .../plugins/phorum/config.default.php | 57 + .../plugins/phorum/htmlpurifier.php | 316 ++ .../htmlpurifier/plugins/phorum/info.txt | 18 + .../plugins/phorum/init-config.php | 30 + .../plugins/phorum/migrate.bbcode.php | 31 + .../htmlpurifier/plugins/phorum/settings.php | 64 + .../plugins/phorum/settings/form.php | 95 + .../phorum/settings/migrate-sigs-form.php | 22 + .../plugins/phorum/settings/migrate-sigs.php | 79 + .../plugins/phorum/settings/save.php | 29 + .../ezyang/htmlpurifier/release1-update.php | 110 + library/ezyang/htmlpurifier/release2-tag.php | 22 + .../htmlpurifier/test-settings.sample.php | 76 + 465 files changed, 22433 insertions(+), 10865 deletions(-) delete mode 100644 library/HTMLPurifier/AttrDef/CSS/AlphaValue.php delete mode 100644 library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php delete mode 100644 library/HTMLPurifier/AttrDef/CSS/FontFamily.php delete mode 100644 library/HTMLPurifier/AttrDef/CSS/Length.php delete mode 100644 library/HTMLPurifier/AttrDef/CSS/ListStyle.php delete mode 100644 library/HTMLPurifier/AttrDef/CSS/Percentage.php delete mode 100644 library/HTMLPurifier/AttrDef/HTML/Bool.php delete mode 100644 library/HTMLPurifier/AttrDef/HTML/Color.php delete mode 100644 library/HTMLPurifier/AttrDef/HTML/FrameTarget.php delete mode 100644 library/HTMLPurifier/AttrDef/HTML/ID.php delete mode 100644 library/HTMLPurifier/AttrDef/HTML/Length.php delete mode 100644 library/HTMLPurifier/AttrDef/HTML/MultiLength.php delete mode 100644 library/HTMLPurifier/AttrDef/HTML/Pixels.php delete mode 100644 library/HTMLPurifier/AttrDef/Text.php delete mode 100644 library/HTMLPurifier/AttrDef/URI/Host.php delete mode 100644 library/HTMLPurifier/AttrDef/URI/IPv6.php delete mode 100644 library/HTMLPurifier/AttrTransform/BoolToCSS.php delete mode 100644 library/HTMLPurifier/AttrTransform/EnumToCSS.php delete mode 100644 library/HTMLPurifier/AttrTransform/Length.php delete mode 100644 library/HTMLPurifier/AttrTransform/Name.php delete mode 100644 library/HTMLPurifier/AttrTransform/NameSync.php delete mode 100644 library/HTMLPurifier/AttrTransform/SafeObject.php delete mode 100644 library/HTMLPurifier/AttrTransform/Textarea.php delete mode 100644 library/HTMLPurifier/Bootstrap.php delete mode 100644 library/HTMLPurifier/CSSDefinition.php delete mode 100644 library/HTMLPurifier/ChildDef/Optional.php delete mode 100644 library/HTMLPurifier/ChildDef/Required.php delete mode 100644 library/HTMLPurifier/ChildDef/StrictBlockquote.php delete mode 100644 library/HTMLPurifier/ChildDef/Table.php delete mode 100644 library/HTMLPurifier/Config.php delete mode 100644 library/HTMLPurifier/ConfigSchema/ValidatorAtom.php delete mode 100644 library/HTMLPurifier/ConfigSchema/schema.ser delete mode 100644 library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt delete mode 100644 library/HTMLPurifier/Context.php delete mode 100644 library/HTMLPurifier/Definition.php delete mode 100644 library/HTMLPurifier/DefinitionCache/Decorator.php delete mode 100644 library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php delete mode 100644 library/HTMLPurifier/DefinitionCache/Decorator/Memory.php delete mode 100644 library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in delete mode 100644 library/HTMLPurifier/DefinitionCache/Null.php delete mode 100644 library/HTMLPurifier/DefinitionCache/Serializer.php delete mode 100644 library/HTMLPurifier/EntityLookup/entities.ser delete mode 100644 library/HTMLPurifier/Filter/ExtractStyleBlocks.php delete mode 100644 library/HTMLPurifier/Filter/YouTube.php delete mode 100644 library/HTMLPurifier/HTMLModule/Forms.php delete mode 100644 library/HTMLPurifier/HTMLModule/Tidy/Strict.php delete mode 100644 library/HTMLPurifier/Injector/RemoveEmpty.php delete mode 100644 library/HTMLPurifier/Language/messages/en.php delete mode 100644 library/HTMLPurifier/Lexer/PEARSax3.php delete mode 100644 library/HTMLPurifier/Lexer/PH5P.php delete mode 100644 library/HTMLPurifier/Strategy/FixNesting.php delete mode 100644 library/HTMLPurifier/Token.php delete mode 100644 library/HTMLPurifier/Token/Comment.php delete mode 100644 library/HTMLPurifier/TokenFactory.php delete mode 100644 library/HTMLPurifier/URI.php delete mode 100644 library/HTMLPurifier/URIFilter.php delete mode 100644 library/HTMLPurifier/URIFilter/DisableExternal.php delete mode 100644 library/HTMLPurifier/URIFilter/DisableExternalResources.php delete mode 100644 library/HTMLPurifier/URIFilter/HostBlacklist.php delete mode 100644 library/HTMLPurifier/URIFilter/Munge.php delete mode 100644 library/HTMLPurifier/URIScheme.php delete mode 100644 library/HTMLPurifier/URIScheme/news.php delete mode 100644 library/HTMLPurifier/URIScheme/nntp.php delete mode 100644 library/HTMLPurifier/VarParser.php create mode 100644 library/ezyang/htmlpurifier/CREDITS create mode 100644 library/ezyang/htmlpurifier/INSTALL create mode 100644 library/ezyang/htmlpurifier/INSTALL.fr.utf8 create mode 100644 library/ezyang/htmlpurifier/LICENSE create mode 100644 library/ezyang/htmlpurifier/NEWS create mode 100644 library/ezyang/htmlpurifier/README create mode 100644 library/ezyang/htmlpurifier/TODO create mode 100644 library/ezyang/htmlpurifier/VERSION create mode 100644 library/ezyang/htmlpurifier/WHATSNEW create mode 100644 library/ezyang/htmlpurifier/WYSIWYG create mode 100644 library/ezyang/htmlpurifier/composer.json create mode 100644 library/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php create mode 100644 library/ezyang/htmlpurifier/extras/FSTools.php create mode 100644 library/ezyang/htmlpurifier/extras/FSTools/File.php create mode 100644 library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php create mode 100644 library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php create mode 100644 library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php create mode 100644 library/ezyang/htmlpurifier/extras/README rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.auto.php (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.autoload.php (70%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier.composer.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.func.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.includes.php (91%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.kses.php (96%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.path.php (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.php (66%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier.safe-includes.php (91%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrCollections.php (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef.php (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS.php (77%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Background.php (63%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php (73%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Border.php (71%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Color.php (54%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Composite.php (61%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Filter.php (62%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Font.php (67%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php (62%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Multiple.php (65%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/Number.php (55%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/TextDecoration.php (69%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/CSS/URI.php (58%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/Enum.php (70%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/HTML/Class.php (66%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/HTML/LinkTypes.php (56%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/HTML/Nmtokens.php (55%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/Integer.php (55%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/Lang.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/Switch.php (62%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/URI.php (53%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/URI/Email.php (73%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php (65%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrDef/URI/IPv4.php (51%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform.php (63%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/Background.php (55%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/BdoDir.php (55%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/BgColor.php (55%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/Border.php (55%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/ImgRequired.php (77%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/ImgSpace.php (60%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/Input.php (61%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/Lang.php (68%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/SafeEmbed.php (56%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/SafeParam.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTransform/ScriptRequired.php (59%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrTypes.php (64%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/AttrValidator.php (74%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ChildDef.php (52%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ChildDef/Chameleon.php (57%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ChildDef/Custom.php (71%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ChildDef/Empty.php (58%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Config.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema.php (69%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php (86%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/Builder/Xml.php (52%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/Exception.php (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/Interchange.php (76%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/Interchange/Directive.php (65%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/Interchange/Id.php (54%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/InterchangeBuilder.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/Validator.php (73%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt (84%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt (62%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.Language.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt (65%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt (54%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt (72%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt (91%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.Base.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt (64%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.Host.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt (93%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ConfigSchema/schema/info.ini (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ContentSets.php (76%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Context.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/DefinitionCache.php (66%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/DefinitionCache/Serializer/README (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/DefinitionCacheFactory.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Doctype.php (77%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/DoctypeRegistry.php (51%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ElementDef.php (69%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Encoder.php (63%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/EntityLookup.php (75%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/EntityParser.php (75%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ErrorCollector.php (82%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/ErrorStruct.php (81%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Exception.php (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Filter.php (71%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Generator.php (56%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLDefinition.php (70%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule.php (71%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Bdo.php (66%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/CommonAttributes.php (89%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Edit.php (71%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Hypertext.php (78%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Image.php (80%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Legacy.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/List.php (57%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Name.php (65%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php (79%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Object.php (71%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Presentation.php (52%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Proprietary.php (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Ruby.php (77%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/SafeEmbed.php (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/SafeObject.php (67%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Scripting.php (76%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/StyleAttribute.php (78%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Tables.php (75%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Target.php (77%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Text.php (58%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Tidy.php (78%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Tidy/Name.php (80%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Tidy/Proprietary.php (85%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Tidy/Transitional.php (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Tidy/XHTML.php (66%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php (50%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModule/XMLCommonAttributes.php (79%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/HTMLModuleManager.php (75%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/IDAccumulator.php (66%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Injector.php (55%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Injector/AutoParagraph.php (88%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Injector/DisplayLinkURI.php (64%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Injector/Linkify.php (72%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Injector/PurifierLinkify.php (58%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Injector/SafeObject.php (78%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Language.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Language/classes/en-x-test.php (97%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Language/messages/en-x-test.php (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Language/messages/en-x-testmini.php (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/LanguageFactory.php (75%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Length.php (56%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Lexer.php (63%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Lexer/DOMLex.php (58%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Lexer/DirectLex.php (76%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Node.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/PercentEncoder.php (78%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Printer.php (54%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Printer/CSSDefinition.php (85%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Printer/ConfigForm.css (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Printer/ConfigForm.js (100%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Printer/ConfigForm.php (60%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Printer/HTMLDefinition.php (53%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/PropertyList.php (50%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/PropertyListIterator.php (60%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Strategy.php (66%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Strategy/Composite.php (61%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Strategy/Core.php (92%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Strategy/MakeWellFormed.php (52%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Strategy/RemoveForeignElements.php (64%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Strategy/ValidateAttributes.php (65%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/StringHash.php (75%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/StringHashParser.php (73%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/TagTransform.php (62%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/TagTransform/Font.php (71%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/TagTransform/Simple.php (61%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Token.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Token/Empty.php (54%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Token/End.php (58%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Token/Start.php (99%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Token/Tag.php (72%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/Token/Text.php (54%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIDefinition.php (70%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIFilter/MakeAbsolute.php (71%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIParser.php (94%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIScheme/data.php (74%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIScheme/ftp.php (74%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIScheme/http.php (50%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIScheme/https.php (65%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URIScheme/mailto.php (63%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/URISchemeRegistry.php (58%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/UnitConverter.php (75%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/VarParser/Flexible.php (56%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/VarParser/Native.php (67%) rename library/{ => ezyang/htmlpurifier/library}/HTMLPurifier/VarParserException.php (100%) create mode 100644 library/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php create mode 100644 library/ezyang/htmlpurifier/package.php create mode 100644 library/ezyang/htmlpurifier/phpdoc.ini create mode 100644 library/ezyang/htmlpurifier/plugins/modx.txt create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/.gitignore create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/Changelog create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/INSTALL create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/README create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/config.default.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/info.txt create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/init-config.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/settings.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/settings/form.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php create mode 100644 library/ezyang/htmlpurifier/plugins/phorum/settings/save.php create mode 100644 library/ezyang/htmlpurifier/release1-update.php create mode 100644 library/ezyang/htmlpurifier/release2-tag.php create mode 100644 library/ezyang/htmlpurifier/test-settings.sample.php diff --git a/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php deleted file mode 100644 index 292c040d4b..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php - -class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number -{ - - public function __construct() { - parent::__construct(false); // opacity is non-negative, but we will clamp it - } - - public function validate($number, $config, $context) { - $result = parent::validate($number, $config, $context); - if ($result === false) return $result; - $float = (float) $result; - if ($float < 0.0) $result = '0'; - if ($float > 1.0) $result = '1'; - return $result; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php deleted file mode 100644 index 6599c5b2dd..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -/** - * Decorator which enables CSS properties to be disabled for specific elements. - */ -class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef -{ - public $def, $element; - - /** - * @param $def Definition to wrap - * @param $element Element to deny - */ - public function __construct($def, $element) { - $this->def = $def; - $this->element = $element; - } - /** - * Checks if CurrentToken is set and equal to $this->element - */ - public function validate($string, $config, $context) { - $token = $context->get('CurrentToken', true); - if ($token && $token->name == $this->element) return false; - return $this->def->validate($string, $config, $context); - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/FontFamily.php b/library/HTMLPurifier/AttrDef/CSS/FontFamily.php deleted file mode 100644 index 42c2054c2a..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/FontFamily.php +++ /dev/null @@ -1,72 +0,0 @@ -<?php - -/** - * Validates a font family list according to CSS spec - * @todo whitelisting allowed fonts would be nice - */ -class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef -{ - - public function validate($string, $config, $context) { - static $generic_names = array( - 'serif' => true, - 'sans-serif' => true, - 'monospace' => true, - 'fantasy' => true, - 'cursive' => true - ); - - // assume that no font names contain commas in them - $fonts = explode(',', $string); - $final = ''; - foreach($fonts as $font) { - $font = trim($font); - if ($font === '') continue; - // match a generic name - if (isset($generic_names[$font])) { - $final .= $font . ', '; - continue; - } - // match a quoted name - if ($font[0] === '"' || $font[0] === "'") { - $length = strlen($font); - if ($length <= 2) continue; - $quote = $font[0]; - if ($font[$length - 1] !== $quote) continue; - $font = substr($font, 1, $length - 2); - } - - $font = $this->expandCSSEscape($font); - - // $font is a pure representation of the font name - - if (ctype_alnum($font) && $font !== '') { - // very simple font, allow it in unharmed - $final .= $font . ', '; - continue; - } - - // bugger out on whitespace. form feed (0C) really - // shouldn't show up regardless - $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); - - // These ugly transforms don't pose a security - // risk (as \\ and \" might). We could try to be clever and - // use single-quote wrapping when there is a double quote - // present, but I have choosen not to implement that. - // (warning: this code relies on the selection of quotation - // mark below) - $font = str_replace('\\', '\\5C ', $font); - $font = str_replace('"', '\\22 ', $font); - - // complicated font, requires quoting - $final .= "\"$font\", "; // note that this will later get turned into " - } - $final = rtrim($final, ', '); - if ($final === '') return false; - return $final; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Length.php b/library/HTMLPurifier/AttrDef/CSS/Length.php deleted file mode 100644 index a07ec58135..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Length.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -/** - * Represents a Length as defined by CSS. - */ -class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef -{ - - protected $min, $max; - - /** - * @param HTMLPurifier_Length $max Minimum length, or null for no bound. String is also acceptable. - * @param HTMLPurifier_Length $max Maximum length, or null for no bound. String is also acceptable. - */ - public function __construct($min = null, $max = null) { - $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null; - $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null; - } - - public function validate($string, $config, $context) { - $string = $this->parseCDATA($string); - - // Optimizations - if ($string === '') return false; - if ($string === '0') return '0'; - if (strlen($string) === 1) return false; - - $length = HTMLPurifier_Length::make($string); - if (!$length->isValid()) return false; - - if ($this->min) { - $c = $length->compareTo($this->min); - if ($c === false) return false; - if ($c < 0) return false; - } - if ($this->max) { - $c = $length->compareTo($this->max); - if ($c === false) return false; - if ($c > 0) return false; - } - - return $length->toString(); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/ListStyle.php b/library/HTMLPurifier/AttrDef/CSS/ListStyle.php deleted file mode 100644 index 4406868c08..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/ListStyle.php +++ /dev/null @@ -1,78 +0,0 @@ -<?php - -/** - * Validates shorthand CSS property list-style. - * @warning Does not support url tokens that have internal spaces. - */ -class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef -{ - - /** - * Local copy of component validators. - * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl. - */ - protected $info; - - public function __construct($config) { - $def = $config->getCSSDefinition(); - $this->info['list-style-type'] = $def->info['list-style-type']; - $this->info['list-style-position'] = $def->info['list-style-position']; - $this->info['list-style-image'] = $def->info['list-style-image']; - } - - public function validate($string, $config, $context) { - - // regular pre-processing - $string = $this->parseCDATA($string); - if ($string === '') return false; - - // assumes URI doesn't have spaces in it - $bits = explode(' ', strtolower($string)); // bits to process - - $caught = array(); - $caught['type'] = false; - $caught['position'] = false; - $caught['image'] = false; - - $i = 0; // number of catches - $none = false; - - foreach ($bits as $bit) { - if ($i >= 3) return; // optimization bit - if ($bit === '') continue; - foreach ($caught as $key => $status) { - if ($status !== false) continue; - $r = $this->info['list-style-' . $key]->validate($bit, $config, $context); - if ($r === false) continue; - if ($r === 'none') { - if ($none) continue; - else $none = true; - if ($key == 'image') continue; - } - $caught[$key] = $r; - $i++; - break; - } - } - - if (!$i) return false; - - $ret = array(); - - // construct type - if ($caught['type']) $ret[] = $caught['type']; - - // construct image - if ($caught['image']) $ret[] = $caught['image']; - - // construct position - if ($caught['position']) $ret[] = $caught['position']; - - if (empty($ret)) return false; - return implode(' ', $ret); - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Percentage.php b/library/HTMLPurifier/AttrDef/CSS/Percentage.php deleted file mode 100644 index c34b8fc3c3..0000000000 --- a/library/HTMLPurifier/AttrDef/CSS/Percentage.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php - -/** - * Validates a Percentage as defined by the CSS spec. - */ -class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef -{ - - /** - * Instance of HTMLPurifier_AttrDef_CSS_Number to defer number validation - */ - protected $number_def; - - /** - * @param Bool indicating whether to forbid negative values - */ - public function __construct($non_negative = false) { - $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); - } - - public function validate($string, $config, $context) { - - $string = $this->parseCDATA($string); - - if ($string === '') return false; - $length = strlen($string); - if ($length === 1) return false; - if ($string[$length - 1] !== '%') return false; - - $number = substr($string, 0, $length - 1); - $number = $this->number_def->validate($number, $config, $context); - - if ($number === false) return false; - return "$number%"; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/Bool.php b/library/HTMLPurifier/AttrDef/HTML/Bool.php deleted file mode 100644 index e06987eb8d..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/Bool.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -/** - * Validates a boolean attribute - */ -class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef -{ - - protected $name; - public $minimized = true; - - public function __construct($name = false) {$this->name = $name;} - - public function validate($string, $config, $context) { - if (empty($string)) return false; - return $this->name; - } - - /** - * @param $string Name of attribute - */ - public function make($string) { - return new HTMLPurifier_AttrDef_HTML_Bool($string); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/Color.php b/library/HTMLPurifier/AttrDef/HTML/Color.php deleted file mode 100644 index d01e20454e..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/Color.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php - -/** - * Validates a color according to the HTML spec. - */ -class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef -{ - - public function validate($string, $config, $context) { - - static $colors = null; - if ($colors === null) $colors = $config->get('Core.ColorKeywords'); - - $string = trim($string); - - if (empty($string)) return false; - if (isset($colors[$string])) return $colors[$string]; - if ($string[0] === '#') $hex = substr($string, 1); - else $hex = $string; - - $length = strlen($hex); - if ($length !== 3 && $length !== 6) return false; - if (!ctype_xdigit($hex)) return false; - if ($length === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2]; - - return "#$hex"; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php deleted file mode 100644 index ae6ea7c01d..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php - -/** - * Special-case enum attribute definition that lazy loads allowed frame targets - */ -class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum -{ - - public $valid_values = false; // uninitialized value - protected $case_sensitive = false; - - public function __construct() {} - - public function validate($string, $config, $context) { - if ($this->valid_values === false) $this->valid_values = $config->get('Attr.AllowedFrameTargets'); - return parent::validate($string, $config, $context); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/ID.php b/library/HTMLPurifier/AttrDef/HTML/ID.php deleted file mode 100644 index 81d03762de..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/ID.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php - -/** - * Validates the HTML attribute ID. - * @warning Even though this is the id processor, it - * will ignore the directive Attr:IDBlacklist, since it will only - * go according to the ID accumulator. Since the accumulator is - * automatically generated, it will have already absorbed the - * blacklist. If you're hacking around, make sure you use load()! - */ - -class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef -{ - - // ref functionality disabled, since we also have to verify - // whether or not the ID it refers to exists - - public function validate($id, $config, $context) { - - if (!$config->get('Attr.EnableID')) return false; - - $id = trim($id); // trim it first - - if ($id === '') return false; - - $prefix = $config->get('Attr.IDPrefix'); - if ($prefix !== '') { - $prefix .= $config->get('Attr.IDPrefixLocal'); - // prevent re-appending the prefix - if (strpos($id, $prefix) !== 0) $id = $prefix . $id; - } elseif ($config->get('Attr.IDPrefixLocal') !== '') { - trigger_error('%Attr.IDPrefixLocal cannot be used unless '. - '%Attr.IDPrefix is set', E_USER_WARNING); - } - - //if (!$this->ref) { - $id_accumulator =& $context->get('IDAccumulator'); - if (isset($id_accumulator->ids[$id])) return false; - //} - - // we purposely avoid using regex, hopefully this is faster - - if (ctype_alpha($id)) { - $result = true; - } else { - if (!ctype_alpha(@$id[0])) return false; - $trim = trim( // primitive style of regexps, I suppose - $id, - 'A..Za..z0..9:-._' - ); - $result = ($trim === ''); - } - - $regexp = $config->get('Attr.IDBlacklistRegexp'); - if ($regexp && preg_match($regexp, $id)) { - return false; - } - - if (/*!$this->ref && */$result) $id_accumulator->add($id); - - // if no change was made to the ID, return the result - // else, return the new id if stripping whitespace made it - // valid, or return false. - return $result ? $id : false; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/Length.php b/library/HTMLPurifier/AttrDef/HTML/Length.php deleted file mode 100644 index a242f9c238..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/Length.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php - -/** - * Validates the HTML type length (not to be confused with CSS's length). - * - * This accepts integer pixels or percentages as lengths for certain - * HTML attributes. - */ - -class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels -{ - - public function validate($string, $config, $context) { - - $string = trim($string); - if ($string === '') return false; - - $parent_result = parent::validate($string, $config, $context); - if ($parent_result !== false) return $parent_result; - - $length = strlen($string); - $last_char = $string[$length - 1]; - - if ($last_char !== '%') return false; - - $points = substr($string, 0, $length - 1); - - if (!is_numeric($points)) return false; - - $points = (int) $points; - - if ($points < 0) return '0%'; - if ($points > 100) return '100%'; - - return ((string) $points) . '%'; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/MultiLength.php b/library/HTMLPurifier/AttrDef/HTML/MultiLength.php deleted file mode 100644 index c72fc76e4d..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/MultiLength.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php - -/** - * Validates a MultiLength as defined by the HTML spec. - * - * A multilength is either a integer (pixel count), a percentage, or - * a relative number. - */ -class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length -{ - - public function validate($string, $config, $context) { - - $string = trim($string); - if ($string === '') return false; - - $parent_result = parent::validate($string, $config, $context); - if ($parent_result !== false) return $parent_result; - - $length = strlen($string); - $last_char = $string[$length - 1]; - - if ($last_char !== '*') return false; - - $int = substr($string, 0, $length - 1); - - if ($int == '') return '*'; - if (!is_numeric($int)) return false; - - $int = (int) $int; - - if ($int < 0) return false; - if ($int == 0) return '0'; - if ($int == 1) return '*'; - return ((string) $int) . '*'; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/Pixels.php b/library/HTMLPurifier/AttrDef/HTML/Pixels.php deleted file mode 100644 index 4cb2c1b857..0000000000 --- a/library/HTMLPurifier/AttrDef/HTML/Pixels.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -/** - * Validates an integer representation of pixels according to the HTML spec. - */ -class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef -{ - - protected $max; - - public function __construct($max = null) { - $this->max = $max; - } - - public function validate($string, $config, $context) { - - $string = trim($string); - if ($string === '0') return $string; - if ($string === '') return false; - $length = strlen($string); - if (substr($string, $length - 2) == 'px') { - $string = substr($string, 0, $length - 2); - } - if (!is_numeric($string)) return false; - $int = (int) $string; - - if ($int < 0) return '0'; - - // upper-bound value, extremely high values can - // crash operating systems, see <http://ha.ckers.org/imagecrash.html> - // WARNING, above link WILL crash you if you're using Windows - - if ($this->max !== null && $int > $this->max) return (string) $this->max; - - return (string) $int; - - } - - public function make($string) { - if ($string === '') $max = null; - else $max = (int) $string; - $class = get_class($this); - return new $class($max); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/Text.php b/library/HTMLPurifier/AttrDef/Text.php deleted file mode 100644 index c6216cc531..0000000000 --- a/library/HTMLPurifier/AttrDef/Text.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -/** - * Validates arbitrary text according to the HTML spec. - */ -class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef -{ - - public function validate($string, $config, $context) { - return $this->parseCDATA($string); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/URI/Host.php b/library/HTMLPurifier/AttrDef/URI/Host.php deleted file mode 100644 index 2156c10c66..0000000000 --- a/library/HTMLPurifier/AttrDef/URI/Host.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -/** - * Validates a host according to the IPv4, IPv6 and DNS (future) specifications. - */ -class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef -{ - - /** - * Instance of HTMLPurifier_AttrDef_URI_IPv4 sub-validator - */ - protected $ipv4; - - /** - * Instance of HTMLPurifier_AttrDef_URI_IPv6 sub-validator - */ - protected $ipv6; - - public function __construct() { - $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); - $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); - } - - public function validate($string, $config, $context) { - $length = strlen($string); - if ($string === '') return ''; - if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') { - //IPv6 - $ip = substr($string, 1, $length - 2); - $valid = $this->ipv6->validate($ip, $config, $context); - if ($valid === false) return false; - return '['. $valid . ']'; - } - - // need to do checks on unusual encodings too - $ipv4 = $this->ipv4->validate($string, $config, $context); - if ($ipv4 !== false) return $ipv4; - - // A regular domain name. - - // This breaks I18N domain names, but we don't have proper IRI support, - // so force users to insert Punycode. If there's complaining we'll - // try to fix things into an international friendly form. - - // The productions describing this are: - $a = '[a-z]'; // alpha - $an = '[a-z0-9]'; // alphanum - $and = '[a-z0-9-]'; // alphanum | "-" - // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum - $domainlabel = "$an($and*$an)?"; - // toplabel = alpha | alpha *( alphanum | "-" ) alphanum - $toplabel = "$a($and*$an)?"; - // hostname = *( domainlabel "." ) toplabel [ "." ] - $match = preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string); - if (!$match) return false; - - return $string; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/URI/IPv6.php b/library/HTMLPurifier/AttrDef/URI/IPv6.php deleted file mode 100644 index 9454e9be50..0000000000 --- a/library/HTMLPurifier/AttrDef/URI/IPv6.php +++ /dev/null @@ -1,99 +0,0 @@ -<?php - -/** - * Validates an IPv6 address. - * @author Feyd @ forums.devnetwork.net (public domain) - * @note This function requires brackets to have been removed from address - * in URI. - */ -class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4 -{ - - public function validate($aIP, $config, $context) { - - if (!$this->ip4) $this->_loadRegex(); - - $original = $aIP; - - $hex = '[0-9a-fA-F]'; - $blk = '(?:' . $hex . '{1,4})'; - $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128 - - // prefix check - if (strpos($aIP, '/') !== false) - { - if (preg_match('#' . $pre . '$#s', $aIP, $find)) - { - $aIP = substr($aIP, 0, 0-strlen($find[0])); - unset($find); - } - else - { - return false; - } - } - - // IPv4-compatiblity check - if (preg_match('#(?<=:'.')' . $this->ip4 . '$#s', $aIP, $find)) - { - $aIP = substr($aIP, 0, 0-strlen($find[0])); - $ip = explode('.', $find[0]); - $ip = array_map('dechex', $ip); - $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3]; - unset($find, $ip); - } - - // compression check - $aIP = explode('::', $aIP); - $c = count($aIP); - if ($c > 2) - { - return false; - } - elseif ($c == 2) - { - list($first, $second) = $aIP; - $first = explode(':', $first); - $second = explode(':', $second); - - if (count($first) + count($second) > 8) - { - return false; - } - - while(count($first) < 8) - { - array_push($first, '0'); - } - - array_splice($first, 8 - count($second), 8, $second); - $aIP = $first; - unset($first,$second); - } - else - { - $aIP = explode(':', $aIP[0]); - } - $c = count($aIP); - - if ($c != 8) - { - return false; - } - - // All the pieces should be 16-bit hex strings. Are they? - foreach ($aIP as $piece) - { - if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) - { - return false; - } - } - - return $original; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/BoolToCSS.php b/library/HTMLPurifier/AttrTransform/BoolToCSS.php deleted file mode 100644 index 51159b6715..0000000000 --- a/library/HTMLPurifier/AttrTransform/BoolToCSS.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -/** - * Pre-transform that changes converts a boolean attribute to fixed CSS - */ -class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform { - - /** - * Name of boolean attribute that is trigger - */ - protected $attr; - - /** - * CSS declarations to add to style, needs trailing semicolon - */ - protected $css; - - /** - * @param $attr string attribute name to convert from - * @param $css string CSS declarations to add to style (needs semicolon) - */ - public function __construct($attr, $css) { - $this->attr = $attr; - $this->css = $css; - } - - public function transform($attr, $config, $context) { - if (!isset($attr[$this->attr])) return $attr; - unset($attr[$this->attr]); - $this->prependCSS($attr, $this->css); - return $attr; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/EnumToCSS.php b/library/HTMLPurifier/AttrTransform/EnumToCSS.php deleted file mode 100644 index 2a5b4514ab..0000000000 --- a/library/HTMLPurifier/AttrTransform/EnumToCSS.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -/** - * Generic pre-transform that converts an attribute with a fixed number of - * values (enumerated) to CSS. - */ -class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform { - - /** - * Name of attribute to transform from - */ - protected $attr; - - /** - * Lookup array of attribute values to CSS - */ - protected $enumToCSS = array(); - - /** - * Case sensitivity of the matching - * @warning Currently can only be guaranteed to work with ASCII - * values. - */ - protected $caseSensitive = false; - - /** - * @param $attr String attribute name to transform from - * @param $enumToCSS Lookup array of attribute values to CSS - * @param $case_sensitive Boolean case sensitivity indicator, default false - */ - public function __construct($attr, $enum_to_css, $case_sensitive = false) { - $this->attr = $attr; - $this->enumToCSS = $enum_to_css; - $this->caseSensitive = (bool) $case_sensitive; - } - - public function transform($attr, $config, $context) { - - if (!isset($attr[$this->attr])) return $attr; - - $value = trim($attr[$this->attr]); - unset($attr[$this->attr]); - - if (!$this->caseSensitive) $value = strtolower($value); - - if (!isset($this->enumToCSS[$value])) { - return $attr; - } - - $this->prependCSS($attr, $this->enumToCSS[$value]); - - return $attr; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Length.php b/library/HTMLPurifier/AttrTransform/Length.php deleted file mode 100644 index ea2f30473d..0000000000 --- a/library/HTMLPurifier/AttrTransform/Length.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php - -/** - * Class for handling width/height length attribute transformations to CSS - */ -class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform -{ - - protected $name; - protected $cssName; - - public function __construct($name, $css_name = null) { - $this->name = $name; - $this->cssName = $css_name ? $css_name : $name; - } - - public function transform($attr, $config, $context) { - if (!isset($attr[$this->name])) return $attr; - $length = $this->confiscateAttr($attr, $this->name); - if(ctype_digit($length)) $length .= 'px'; - $this->prependCSS($attr, $this->cssName . ":$length;"); - return $attr; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Name.php b/library/HTMLPurifier/AttrTransform/Name.php deleted file mode 100644 index 15315bc735..0000000000 --- a/library/HTMLPurifier/AttrTransform/Name.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php - -/** - * Pre-transform that changes deprecated name attribute to ID if necessary - */ -class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform -{ - - public function transform($attr, $config, $context) { - // Abort early if we're using relaxed definition of name - if ($config->get('HTML.Attr.Name.UseCDATA')) return $attr; - if (!isset($attr['name'])) return $attr; - $id = $this->confiscateAttr($attr, 'name'); - if ( isset($attr['id'])) return $attr; - $attr['id'] = $id; - return $attr; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/NameSync.php b/library/HTMLPurifier/AttrTransform/NameSync.php deleted file mode 100644 index a95638c140..0000000000 --- a/library/HTMLPurifier/AttrTransform/NameSync.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php - -/** - * Post-transform that performs validation to the name attribute; if - * it is present with an equivalent id attribute, it is passed through; - * otherwise validation is performed. - */ -class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform -{ - - public function __construct() { - $this->idDef = new HTMLPurifier_AttrDef_HTML_ID(); - } - - public function transform($attr, $config, $context) { - if (!isset($attr['name'])) return $attr; - $name = $attr['name']; - if (isset($attr['id']) && $attr['id'] === $name) return $attr; - $result = $this->idDef->validate($name, $config, $context); - if ($result === false) unset($attr['name']); - else $attr['name'] = $result; - return $attr; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/SafeObject.php b/library/HTMLPurifier/AttrTransform/SafeObject.php deleted file mode 100644 index 1ed74898ba..0000000000 --- a/library/HTMLPurifier/AttrTransform/SafeObject.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php - -/** - * Writes default type for all objects. Currently only supports flash. - */ -class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform -{ - public $name = "SafeObject"; - - function transform($attr, $config, $context) { - if (!isset($attr['type'])) $attr['type'] = 'application/x-shockwave-flash'; - return $attr; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Textarea.php b/library/HTMLPurifier/AttrTransform/Textarea.php deleted file mode 100644 index 81ac3488ba..0000000000 --- a/library/HTMLPurifier/AttrTransform/Textarea.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -/** - * Sets height/width defaults for <textarea> - */ -class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform -{ - - public function transform($attr, $config, $context) { - // Calculated from Firefox - if (!isset($attr['cols'])) $attr['cols'] = '22'; - if (!isset($attr['rows'])) $attr['rows'] = '3'; - return $attr; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Bootstrap.php b/library/HTMLPurifier/Bootstrap.php deleted file mode 100644 index 559f61a232..0000000000 --- a/library/HTMLPurifier/Bootstrap.php +++ /dev/null @@ -1,98 +0,0 @@ -<?php - -// constants are slow, so we use as few as possible -if (!defined('HTMLPURIFIER_PREFIX')) { - define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..')); -} - -// accomodations for versions earlier than 5.0.2 -// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net> -if (!defined('PHP_EOL')) { - switch (strtoupper(substr(PHP_OS, 0, 3))) { - case 'WIN': - define('PHP_EOL', "\r\n"); - break; - case 'DAR': - define('PHP_EOL', "\r"); - break; - default: - define('PHP_EOL', "\n"); - } -} - -/** - * Bootstrap class that contains meta-functionality for HTML Purifier such as - * the autoload function. - * - * @note - * This class may be used without any other files from HTML Purifier. - */ -class HTMLPurifier_Bootstrap -{ - - /** - * Autoload function for HTML Purifier - * @param $class Class to load - */ - public static function autoload($class) { - $file = HTMLPurifier_Bootstrap::getPath($class); - if (!$file) return false; - require HTMLPURIFIER_PREFIX . '/' . $file; - return true; - } - - /** - * Returns the path for a specific class. - */ - public static function getPath($class) { - if (strncmp('HTMLPurifier', $class, 12) !== 0) return false; - // Custom implementations - if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { - $code = str_replace('_', '-', substr($class, 22)); - $file = 'HTMLPurifier/Language/classes/' . $code . '.php'; - } else { - $file = str_replace('_', '/', $class) . '.php'; - } - if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false; - return $file; - } - - /** - * "Pre-registers" our autoloader on the SPL stack. - */ - public static function registerAutoload() { - $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); - if ( ($funcs = spl_autoload_functions()) === false ) { - spl_autoload_register($autoload); - } elseif (function_exists('spl_autoload_unregister')) { - $compat = version_compare(PHP_VERSION, '5.1.2', '<=') && - version_compare(PHP_VERSION, '5.1.0', '>='); - foreach ($funcs as $func) { - if (is_array($func)) { - // :TRICKY: There are some compatibility issues and some - // places where we need to error out - $reflector = new ReflectionMethod($func[0], $func[1]); - if (!$reflector->isStatic()) { - throw new Exception(' - HTML Purifier autoloader registrar is not compatible - with non-static object methods due to PHP Bug #44144; - Please do not use HTMLPurifier.autoload.php (or any - file that includes this file); instead, place the code: - spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\')) - after your own autoloaders. - '); - } - // Suprisingly, spl_autoload_register supports the - // Class::staticMethod callback format, although call_user_func doesn't - if ($compat) $func = implode('::', $func); - } - spl_autoload_unregister($func); - } - spl_autoload_register($autoload); - foreach ($funcs as $func) spl_autoload_register($func); - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/CSSDefinition.php b/library/HTMLPurifier/CSSDefinition.php deleted file mode 100644 index 6a2e6f56d9..0000000000 --- a/library/HTMLPurifier/CSSDefinition.php +++ /dev/null @@ -1,292 +0,0 @@ -<?php - -/** - * Defines allowed CSS attributes and what their values are. - * @see HTMLPurifier_HTMLDefinition - */ -class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition -{ - - public $type = 'CSS'; - - /** - * Assoc array of attribute name to definition object. - */ - public $info = array(); - - /** - * Constructs the info array. The meat of this class. - */ - protected function doSetup($config) { - - $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( - array('left', 'right', 'center', 'justify'), false); - - $border_style = - $this->info['border-bottom-style'] = - $this->info['border-right-style'] = - $this->info['border-left-style'] = - $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double', - 'groove', 'ridge', 'inset', 'outset'), false); - - $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); - - $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'left', 'right', 'both'), false); - $this->info['float'] = new HTMLPurifier_AttrDef_Enum( - array('none', 'left', 'right'), false); - $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'italic', 'oblique'), false); - $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'small-caps'), false); - - $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( - array( - new HTMLPurifier_AttrDef_Enum(array('none')), - new HTMLPurifier_AttrDef_CSS_URI() - ) - ); - - $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( - array('inside', 'outside'), false); - $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( - array('disc', 'circle', 'square', 'decimal', 'lower-roman', - 'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false); - $this->info['list-style-image'] = $uri_or_none; - - $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); - - $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( - array('capitalize', 'uppercase', 'lowercase', 'none'), false); - $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); - - $this->info['background-image'] = $uri_or_none; - $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( - array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') - ); - $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( - array('scroll', 'fixed') - ); - $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); - - $border_color = - $this->info['border-top-color'] = - $this->info['border-bottom-color'] = - $this->info['border-left-color'] = - $this->info['border-right-color'] = - $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('transparent')), - new HTMLPurifier_AttrDef_CSS_Color() - )); - - $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); - - $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); - - $border_width = - $this->info['border-top-width'] = - $this->info['border-bottom-width'] = - $this->info['border-left-width'] = - $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), - new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative - )); - - $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); - - $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('normal')), - new HTMLPurifier_AttrDef_CSS_Length() - )); - - $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('normal')), - new HTMLPurifier_AttrDef_CSS_Length() - )); - - $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small', - 'small', 'medium', 'large', 'x-large', 'xx-large', - 'larger', 'smaller')), - new HTMLPurifier_AttrDef_CSS_Percentage(), - new HTMLPurifier_AttrDef_CSS_Length() - )); - - $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('normal')), - new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives - new HTMLPurifier_AttrDef_CSS_Length('0'), - new HTMLPurifier_AttrDef_CSS_Percentage(true) - )); - - $margin = - $this->info['margin-top'] = - $this->info['margin-bottom'] = - $this->info['margin-left'] = - $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length(), - new HTMLPurifier_AttrDef_CSS_Percentage(), - new HTMLPurifier_AttrDef_Enum(array('auto')) - )); - - $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); - - // non-negative - $padding = - $this->info['padding-top'] = - $this->info['padding-bottom'] = - $this->info['padding-left'] = - $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length('0'), - new HTMLPurifier_AttrDef_CSS_Percentage(true) - )); - - $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); - - $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length(), - new HTMLPurifier_AttrDef_CSS_Percentage() - )); - - $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length('0'), - new HTMLPurifier_AttrDef_CSS_Percentage(true), - new HTMLPurifier_AttrDef_Enum(array('auto')) - )); - $max = $config->get('CSS.MaxImgLength'); - - $this->info['width'] = - $this->info['height'] = - $max === null ? - $trusted_wh : - new HTMLPurifier_AttrDef_Switch('img', - // For img tags: - new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_CSS_Length('0', $max), - new HTMLPurifier_AttrDef_Enum(array('auto')) - )), - // For everyone else: - $trusted_wh - ); - - $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); - - $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); - - // this could use specialized code - $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( - array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300', - '400', '500', '600', '700', '800', '900'), false); - - // MUST be called after other font properties, as it references - // a CSSDefinition object - $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); - - // same here - $this->info['border'] = - $this->info['border-bottom'] = - $this->info['border-top'] = - $this->info['border-left'] = - $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); - - $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array( - 'collapse', 'separate')); - - $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array( - 'top', 'bottom')); - - $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array( - 'auto', 'fixed')); - - $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array( - new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super', - 'top', 'text-top', 'middle', 'bottom', 'text-bottom')), - new HTMLPurifier_AttrDef_CSS_Length(), - new HTMLPurifier_AttrDef_CSS_Percentage() - )); - - $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); - - // partial support - $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap')); - - if ($config->get('CSS.Proprietary')) { - $this->doSetupProprietary($config); - } - - if ($config->get('CSS.AllowTricky')) { - $this->doSetupTricky($config); - } - - $allow_important = $config->get('CSS.AllowImportant'); - // wrap all attr-defs with decorator that handles !important - foreach ($this->info as $k => $v) { - $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); - } - - $this->setupConfigStuff($config); - } - - protected function doSetupProprietary($config) { - // Internet Explorer only scrollbar colors - $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); - - // technically not proprietary, but CSS3, and no one supports it - $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); - $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); - $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); - - // only opacity, for now - $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); - - } - - protected function doSetupTricky($config) { - $this->info['display'] = new HTMLPurifier_AttrDef_Enum(array( - 'inline', 'block', 'list-item', 'run-in', 'compact', - 'marker', 'table', 'inline-table', 'table-row-group', - 'table-header-group', 'table-footer-group', 'table-row', - 'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none' - )); - $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array( - 'visible', 'hidden', 'collapse' - )); - $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); - } - - - /** - * Performs extra config-based processing. Based off of - * HTMLPurifier_HTMLDefinition. - * @todo Refactor duplicate elements into common class (probably using - * composition, not inheritance). - */ - protected function setupConfigStuff($config) { - - // setup allowed elements - $support = "(for information on implementing this, see the ". - "support forums) "; - $allowed_attributes = $config->get('CSS.AllowedProperties'); - if ($allowed_attributes !== null) { - foreach ($this->info as $name => $d) { - if(!isset($allowed_attributes[$name])) unset($this->info[$name]); - unset($allowed_attributes[$name]); - } - // emit errors - foreach ($allowed_attributes as $name => $d) { - // :TODO: Is this htmlspecialchars() call really necessary? - $name = htmlspecialchars($name); - trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); - } - } - - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/Optional.php b/library/HTMLPurifier/ChildDef/Optional.php deleted file mode 100644 index 32bcb9898e..0000000000 --- a/library/HTMLPurifier/ChildDef/Optional.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php - -/** - * Definition that allows a set of elements, and allows no children. - * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required, - * really, one shouldn't inherit from the other. Only altered behavior - * is to overload a returned false with an array. Thus, it will never - * return false. - */ -class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required -{ - public $allow_empty = true; - public $type = 'optional'; - public function validateChildren($tokens_of_children, $config, $context) { - $result = parent::validateChildren($tokens_of_children, $config, $context); - // we assume that $tokens_of_children is not modified - if ($result === false) { - if (empty($tokens_of_children)) return true; - elseif ($this->whitespace) return $tokens_of_children; - else return array(); - } - return $result; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/Required.php b/library/HTMLPurifier/ChildDef/Required.php deleted file mode 100644 index 4889f249b8..0000000000 --- a/library/HTMLPurifier/ChildDef/Required.php +++ /dev/null @@ -1,117 +0,0 @@ -<?php - -/** - * Definition that allows a set of elements, but disallows empty children. - */ -class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef -{ - /** - * Lookup table of allowed elements. - * @public - */ - public $elements = array(); - /** - * Whether or not the last passed node was all whitespace. - */ - protected $whitespace = false; - /** - * @param $elements List of allowed element names (lowercase). - */ - public function __construct($elements) { - if (is_string($elements)) { - $elements = str_replace(' ', '', $elements); - $elements = explode('|', $elements); - } - $keys = array_keys($elements); - if ($keys == array_keys($keys)) { - $elements = array_flip($elements); - foreach ($elements as $i => $x) { - $elements[$i] = true; - if (empty($i)) unset($elements[$i]); // remove blank - } - } - $this->elements = $elements; - } - public $allow_empty = false; - public $type = 'required'; - public function validateChildren($tokens_of_children, $config, $context) { - // Flag for subclasses - $this->whitespace = false; - - // if there are no tokens, delete parent node - if (empty($tokens_of_children)) return false; - - // the new set of children - $result = array(); - - // current depth into the nest - $nesting = 0; - - // whether or not we're deleting a node - $is_deleting = false; - - // whether or not parsed character data is allowed - // this controls whether or not we silently drop a tag - // or generate escaped HTML from it - $pcdata_allowed = isset($this->elements['#PCDATA']); - - // a little sanity check to make sure it's not ALL whitespace - $all_whitespace = true; - - // some configuration - $escape_invalid_children = $config->get('Core.EscapeInvalidChildren'); - - // generator - $gen = new HTMLPurifier_Generator($config, $context); - - foreach ($tokens_of_children as $token) { - if (!empty($token->is_whitespace)) { - $result[] = $token; - continue; - } - $all_whitespace = false; // phew, we're not talking about whitespace - - $is_child = ($nesting == 0); - - if ($token instanceof HTMLPurifier_Token_Start) { - $nesting++; - } elseif ($token instanceof HTMLPurifier_Token_End) { - $nesting--; - } - - if ($is_child) { - $is_deleting = false; - if (!isset($this->elements[$token->name])) { - $is_deleting = true; - if ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text) { - $result[] = $token; - } elseif ($pcdata_allowed && $escape_invalid_children) { - $result[] = new HTMLPurifier_Token_Text( - $gen->generateFromToken($token) - ); - } - continue; - } - } - if (!$is_deleting || ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text)) { - $result[] = $token; - } elseif ($pcdata_allowed && $escape_invalid_children) { - $result[] = - new HTMLPurifier_Token_Text( - $gen->generateFromToken($token) - ); - } else { - // drop silently - } - } - if (empty($result)) return false; - if ($all_whitespace) { - $this->whitespace = true; - return false; - } - if ($tokens_of_children == $result) return true; - return $result; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/library/HTMLPurifier/ChildDef/StrictBlockquote.php deleted file mode 100644 index dfae8a6e5e..0000000000 --- a/library/HTMLPurifier/ChildDef/StrictBlockquote.php +++ /dev/null @@ -1,88 +0,0 @@ -<?php - -/** - * Takes the contents of blockquote when in strict and reformats for validation. - */ -class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required -{ - protected $real_elements; - protected $fake_elements; - public $allow_empty = true; - public $type = 'strictblockquote'; - protected $init = false; - - /** - * @note We don't want MakeWellFormed to auto-close inline elements since - * they might be allowed. - */ - public function getAllowedElements($config) { - $this->init($config); - return $this->fake_elements; - } - - public function validateChildren($tokens_of_children, $config, $context) { - - $this->init($config); - - // trick the parent class into thinking it allows more - $this->elements = $this->fake_elements; - $result = parent::validateChildren($tokens_of_children, $config, $context); - $this->elements = $this->real_elements; - - if ($result === false) return array(); - if ($result === true) $result = $tokens_of_children; - - $def = $config->getHTMLDefinition(); - $block_wrap_start = new HTMLPurifier_Token_Start($def->info_block_wrapper); - $block_wrap_end = new HTMLPurifier_Token_End( $def->info_block_wrapper); - $is_inline = false; - $depth = 0; - $ret = array(); - - // assuming that there are no comment tokens - foreach ($result as $i => $token) { - $token = $result[$i]; - // ifs are nested for readability - if (!$is_inline) { - if (!$depth) { - if ( - ($token instanceof HTMLPurifier_Token_Text && !$token->is_whitespace) || - (!$token instanceof HTMLPurifier_Token_Text && !isset($this->elements[$token->name])) - ) { - $is_inline = true; - $ret[] = $block_wrap_start; - } - } - } else { - if (!$depth) { - // starting tokens have been inline text / empty - if ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) { - if (isset($this->elements[$token->name])) { - // ended - $ret[] = $block_wrap_end; - $is_inline = false; - } - } - } - } - $ret[] = $token; - if ($token instanceof HTMLPurifier_Token_Start) $depth++; - if ($token instanceof HTMLPurifier_Token_End) $depth--; - } - if ($is_inline) $ret[] = $block_wrap_end; - return $ret; - } - - private function init($config) { - if (!$this->init) { - $def = $config->getHTMLDefinition(); - // allow all inline elements - $this->real_elements = $this->elements; - $this->fake_elements = $def->info_content_sets['Flow']; - $this->fake_elements['#PCDATA'] = true; - $this->init = true; - } - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/Table.php b/library/HTMLPurifier/ChildDef/Table.php deleted file mode 100644 index 34f0227dd2..0000000000 --- a/library/HTMLPurifier/ChildDef/Table.php +++ /dev/null @@ -1,142 +0,0 @@ -<?php - -/** - * Definition for tables - */ -class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef -{ - public $allow_empty = false; - public $type = 'table'; - public $elements = array('tr' => true, 'tbody' => true, 'thead' => true, - 'tfoot' => true, 'caption' => true, 'colgroup' => true, 'col' => true); - public function __construct() {} - public function validateChildren($tokens_of_children, $config, $context) { - if (empty($tokens_of_children)) return false; - - // this ensures that the loop gets run one last time before closing - // up. It's a little bit of a hack, but it works! Just make sure you - // get rid of the token later. - $tokens_of_children[] = false; - - // only one of these elements is allowed in a table - $caption = false; - $thead = false; - $tfoot = false; - - // as many of these as you want - $cols = array(); - $content = array(); - - $nesting = 0; // current depth so we can determine nodes - $is_collecting = false; // are we globbing together tokens to package - // into one of the collectors? - $collection = array(); // collected nodes - $tag_index = 0; // the first node might be whitespace, - // so this tells us where the start tag is - - foreach ($tokens_of_children as $token) { - $is_child = ($nesting == 0); - - if ($token === false) { - // terminating sequence started - } elseif ($token instanceof HTMLPurifier_Token_Start) { - $nesting++; - } elseif ($token instanceof HTMLPurifier_Token_End) { - $nesting--; - } - - // handle node collection - if ($is_collecting) { - if ($is_child) { - // okay, let's stash the tokens away - // first token tells us the type of the collection - switch ($collection[$tag_index]->name) { - case 'tr': - case 'tbody': - $content[] = $collection; - break; - case 'caption': - if ($caption !== false) break; - $caption = $collection; - break; - case 'thead': - case 'tfoot': - // access the appropriate variable, $thead or $tfoot - $var = $collection[$tag_index]->name; - if ($$var === false) { - $$var = $collection; - } else { - // transmutate the first and less entries into - // tbody tags, and then put into content - $collection[$tag_index]->name = 'tbody'; - $collection[count($collection)-1]->name = 'tbody'; - $content[] = $collection; - } - break; - case 'colgroup': - $cols[] = $collection; - break; - } - $collection = array(); - $is_collecting = false; - $tag_index = 0; - } else { - // add the node to the collection - $collection[] = $token; - } - } - - // terminate - if ($token === false) break; - - if ($is_child) { - // determine what we're dealing with - if ($token->name == 'col') { - // the only empty tag in the possie, we can handle it - // immediately - $cols[] = array_merge($collection, array($token)); - $collection = array(); - $tag_index = 0; - continue; - } - switch($token->name) { - case 'caption': - case 'colgroup': - case 'thead': - case 'tfoot': - case 'tbody': - case 'tr': - $is_collecting = true; - $collection[] = $token; - continue; - default: - if (!empty($token->is_whitespace)) { - $collection[] = $token; - $tag_index++; - } - continue; - } - } - } - - if (empty($content)) return false; - - $ret = array(); - if ($caption !== false) $ret = array_merge($ret, $caption); - if ($cols !== false) foreach ($cols as $token_array) $ret = array_merge($ret, $token_array); - if ($thead !== false) $ret = array_merge($ret, $thead); - if ($tfoot !== false) $ret = array_merge($ret, $tfoot); - foreach ($content as $token_array) $ret = array_merge($ret, $token_array); - if (!empty($collection) && $is_collecting == false){ - // grab the trailing space - $ret = array_merge($ret, $collection); - } - - array_pop($tokens_of_children); // remove phantom token - - return ($ret === $tokens_of_children) ? true : $ret; - - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Config.php b/library/HTMLPurifier/Config.php deleted file mode 100644 index 2a334b0d83..0000000000 --- a/library/HTMLPurifier/Config.php +++ /dev/null @@ -1,580 +0,0 @@ -<?php - -/** - * Configuration object that triggers customizable behavior. - * - * @warning This class is strongly defined: that means that the class - * will fail if an undefined directive is retrieved or set. - * - * @note Many classes that could (although many times don't) use the - * configuration object make it a mandatory parameter. This is - * because a configuration object should always be forwarded, - * otherwise, you run the risk of missing a parameter and then - * being stumped when a configuration directive doesn't work. - * - * @todo Reconsider some of the public member variables - */ -class HTMLPurifier_Config -{ - - /** - * HTML Purifier's version - */ - public $version = '4.1.1'; - - /** - * Bool indicator whether or not to automatically finalize - * the object if a read operation is done - */ - public $autoFinalize = true; - - // protected member variables - - /** - * Namespace indexed array of serials for specific namespaces (see - * getSerial() for more info). - */ - protected $serials = array(); - - /** - * Serial for entire configuration object - */ - protected $serial; - - /** - * Parser for variables - */ - protected $parser; - - /** - * Reference HTMLPurifier_ConfigSchema for value checking - * @note This is public for introspective purposes. Please don't - * abuse! - */ - public $def; - - /** - * Indexed array of definitions - */ - protected $definitions; - - /** - * Bool indicator whether or not config is finalized - */ - protected $finalized = false; - - /** - * Property list containing configuration directives. - */ - protected $plist; - - /** - * Whether or not a set is taking place due to an - * alias lookup. - */ - private $aliasMode; - - /** - * Set to false if you do not want line and file numbers in errors - * (useful when unit testing) - */ - public $chatty = true; - - /** - * Current lock; only gets to this namespace are allowed. - */ - private $lock; - - /** - * @param $definition HTMLPurifier_ConfigSchema that defines what directives - * are allowed. - */ - public function __construct($definition, $parent = null) { - $parent = $parent ? $parent : $definition->defaultPlist; - $this->plist = new HTMLPurifier_PropertyList($parent); - $this->def = $definition; // keep a copy around for checking - $this->parser = new HTMLPurifier_VarParser_Flexible(); - } - - /** - * Convenience constructor that creates a config object based on a mixed var - * @param mixed $config Variable that defines the state of the config - * object. Can be: a HTMLPurifier_Config() object, - * an array of directives based on loadArray(), - * or a string filename of an ini file. - * @param HTMLPurifier_ConfigSchema Schema object - * @return Configured HTMLPurifier_Config object - */ - public static function create($config, $schema = null) { - if ($config instanceof HTMLPurifier_Config) { - // pass-through - return $config; - } - if (!$schema) { - $ret = HTMLPurifier_Config::createDefault(); - } else { - $ret = new HTMLPurifier_Config($schema); - } - if (is_string($config)) $ret->loadIni($config); - elseif (is_array($config)) $ret->loadArray($config); - return $ret; - } - - /** - * Creates a new config object that inherits from a previous one. - * @param HTMLPurifier_Config $config Configuration object to inherit - * from. - * @return HTMLPurifier_Config object with $config as its parent. - */ - public static function inherit(HTMLPurifier_Config $config) { - return new HTMLPurifier_Config($config->def, $config->plist); - } - - /** - * Convenience constructor that creates a default configuration object. - * @return Default HTMLPurifier_Config object. - */ - public static function createDefault() { - $definition = HTMLPurifier_ConfigSchema::instance(); - $config = new HTMLPurifier_Config($definition); - return $config; - } - - /** - * Retreives a value from the configuration. - * @param $key String key - */ - public function get($key, $a = null) { - if ($a !== null) { - $this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING); - $key = "$key.$a"; - } - if (!$this->finalized) $this->autoFinalize(); - if (!isset($this->def->info[$key])) { - // can't add % due to SimpleTest bug - $this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key), - E_USER_WARNING); - return; - } - if (isset($this->def->info[$key]->isAlias)) { - $d = $this->def->info[$key]; - $this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key, - E_USER_ERROR); - return; - } - if ($this->lock) { - list($ns) = explode('.', $key); - if ($ns !== $this->lock) { - $this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR); - return; - } - } - return $this->plist->get($key); - } - - /** - * Retreives an array of directives to values from a given namespace - * @param $namespace String namespace - */ - public function getBatch($namespace) { - if (!$this->finalized) $this->autoFinalize(); - $full = $this->getAll(); - if (!isset($full[$namespace])) { - $this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace), - E_USER_WARNING); - return; - } - return $full[$namespace]; - } - - /** - * Returns a md5 signature of a segment of the configuration object - * that uniquely identifies that particular configuration - * @note Revision is handled specially and is removed from the batch - * before processing! - * @param $namespace Namespace to get serial for - */ - public function getBatchSerial($namespace) { - if (empty($this->serials[$namespace])) { - $batch = $this->getBatch($namespace); - unset($batch['DefinitionRev']); - $this->serials[$namespace] = md5(serialize($batch)); - } - return $this->serials[$namespace]; - } - - /** - * Returns a md5 signature for the entire configuration object - * that uniquely identifies that particular configuration - */ - public function getSerial() { - if (empty($this->serial)) { - $this->serial = md5(serialize($this->getAll())); - } - return $this->serial; - } - - /** - * Retrieves all directives, organized by namespace - * @warning This is a pretty inefficient function, avoid if you can - */ - public function getAll() { - if (!$this->finalized) $this->autoFinalize(); - $ret = array(); - foreach ($this->plist->squash() as $name => $value) { - list($ns, $key) = explode('.', $name, 2); - $ret[$ns][$key] = $value; - } - return $ret; - } - - /** - * Sets a value to configuration. - * @param $key String key - * @param $value Mixed value - */ - public function set($key, $value, $a = null) { - if (strpos($key, '.') === false) { - $namespace = $key; - $directive = $value; - $value = $a; - $key = "$key.$directive"; - $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); - } else { - list($namespace) = explode('.', $key); - } - if ($this->isFinalized('Cannot set directive after finalization')) return; - if (!isset($this->def->info[$key])) { - $this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', - E_USER_WARNING); - return; - } - $def = $this->def->info[$key]; - - if (isset($def->isAlias)) { - if ($this->aliasMode) { - $this->triggerError('Double-aliases not allowed, please fix '. - 'ConfigSchema bug with' . $key, E_USER_ERROR); - return; - } - $this->aliasMode = true; - $this->set($def->key, $value); - $this->aliasMode = false; - $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); - return; - } - - // Raw type might be negative when using the fully optimized form - // of stdclass, which indicates allow_null == true - $rtype = is_int($def) ? $def : $def->type; - if ($rtype < 0) { - $type = -$rtype; - $allow_null = true; - } else { - $type = $rtype; - $allow_null = isset($def->allow_null); - } - - try { - $value = $this->parser->parse($value, $type, $allow_null); - } catch (HTMLPurifier_VarParserException $e) { - $this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING); - return; - } - if (is_string($value) && is_object($def)) { - // resolve value alias if defined - if (isset($def->aliases[$value])) { - $value = $def->aliases[$value]; - } - // check to see if the value is allowed - if (isset($def->allowed) && !isset($def->allowed[$value])) { - $this->triggerError('Value not supported, valid values are: ' . - $this->_listify($def->allowed), E_USER_WARNING); - return; - } - } - $this->plist->set($key, $value); - - // reset definitions if the directives they depend on changed - // this is a very costly process, so it's discouraged - // with finalization - if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { - $this->definitions[$namespace] = null; - } - - $this->serials[$namespace] = false; - } - - /** - * Convenience function for error reporting - */ - private function _listify($lookup) { - $list = array(); - foreach ($lookup as $name => $b) $list[] = $name; - return implode(', ', $list); - } - - /** - * Retrieves object reference to the HTML definition. - * @param $raw Return a copy that has not been setup yet. Must be - * called before it's been setup, otherwise won't work. - */ - public function getHTMLDefinition($raw = false) { - return $this->getDefinition('HTML', $raw); - } - - /** - * Retrieves object reference to the CSS definition - * @param $raw Return a copy that has not been setup yet. Must be - * called before it's been setup, otherwise won't work. - */ - public function getCSSDefinition($raw = false) { - return $this->getDefinition('CSS', $raw); - } - - /** - * Retrieves a definition - * @param $type Type of definition: HTML, CSS, etc - * @param $raw Whether or not definition should be returned raw - */ - public function getDefinition($type, $raw = false) { - if (!$this->finalized) $this->autoFinalize(); - // temporarily suspend locks, so we can handle recursive definition calls - $lock = $this->lock; - $this->lock = null; - $factory = HTMLPurifier_DefinitionCacheFactory::instance(); - $cache = $factory->create($type, $this); - $this->lock = $lock; - if (!$raw) { - // see if we can quickly supply a definition - if (!empty($this->definitions[$type])) { - if (!$this->definitions[$type]->setup) { - $this->definitions[$type]->setup($this); - $cache->set($this->definitions[$type], $this); - } - return $this->definitions[$type]; - } - // memory check missed, try cache - $this->definitions[$type] = $cache->get($this); - if ($this->definitions[$type]) { - // definition in cache, return it - return $this->definitions[$type]; - } - } elseif ( - !empty($this->definitions[$type]) && - !$this->definitions[$type]->setup - ) { - // raw requested, raw in memory, quick return - return $this->definitions[$type]; - } - // quick checks failed, let's create the object - if ($type == 'HTML') { - $this->definitions[$type] = new HTMLPurifier_HTMLDefinition(); - } elseif ($type == 'CSS') { - $this->definitions[$type] = new HTMLPurifier_CSSDefinition(); - } elseif ($type == 'URI') { - $this->definitions[$type] = new HTMLPurifier_URIDefinition(); - } else { - throw new HTMLPurifier_Exception("Definition of $type type not supported"); - } - // quick abort if raw - if ($raw) { - if (is_null($this->get($type . '.DefinitionID'))) { - // fatally error out if definition ID not set - throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID"); - } - return $this->definitions[$type]; - } - // set it up - $this->lock = $type; - $this->definitions[$type]->setup($this); - $this->lock = null; - // save in cache - $cache->set($this->definitions[$type], $this); - return $this->definitions[$type]; - } - - /** - * Loads configuration values from an array with the following structure: - * Namespace.Directive => Value - * @param $config_array Configuration associative array - */ - public function loadArray($config_array) { - if ($this->isFinalized('Cannot load directives after finalization')) return; - foreach ($config_array as $key => $value) { - $key = str_replace('_', '.', $key); - if (strpos($key, '.') !== false) { - $this->set($key, $value); - } else { - $namespace = $key; - $namespace_values = $value; - foreach ($namespace_values as $directive => $value) { - $this->set($namespace .'.'. $directive, $value); - } - } - } - } - - /** - * Returns a list of array(namespace, directive) for all directives - * that are allowed in a web-form context as per an allowed - * namespaces/directives list. - * @param $allowed List of allowed namespaces/directives - */ - public static function getAllowedDirectivesForForm($allowed, $schema = null) { - if (!$schema) { - $schema = HTMLPurifier_ConfigSchema::instance(); - } - if ($allowed !== true) { - if (is_string($allowed)) $allowed = array($allowed); - $allowed_ns = array(); - $allowed_directives = array(); - $blacklisted_directives = array(); - foreach ($allowed as $ns_or_directive) { - if (strpos($ns_or_directive, '.') !== false) { - // directive - if ($ns_or_directive[0] == '-') { - $blacklisted_directives[substr($ns_or_directive, 1)] = true; - } else { - $allowed_directives[$ns_or_directive] = true; - } - } else { - // namespace - $allowed_ns[$ns_or_directive] = true; - } - } - } - $ret = array(); - foreach ($schema->info as $key => $def) { - list($ns, $directive) = explode('.', $key, 2); - if ($allowed !== true) { - if (isset($blacklisted_directives["$ns.$directive"])) continue; - if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue; - } - if (isset($def->isAlias)) continue; - if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue; - $ret[] = array($ns, $directive); - } - return $ret; - } - - /** - * Loads configuration values from $_GET/$_POST that were posted - * via ConfigForm - * @param $array $_GET or $_POST array to import - * @param $index Index/name that the config variables are in - * @param $allowed List of allowed namespaces/directives - * @param $mq_fix Boolean whether or not to enable magic quotes fix - * @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy - */ - public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { - $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); - $config = HTMLPurifier_Config::create($ret, $schema); - return $config; - } - - /** - * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. - * @note Same parameters as loadArrayFromForm - */ - public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) { - $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); - $this->loadArray($ret); - } - - /** - * Prepares an array from a form into something usable for the more - * strict parts of HTMLPurifier_Config - */ - public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) { - if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); - $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); - - $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); - $ret = array(); - foreach ($allowed as $key) { - list($ns, $directive) = $key; - $skey = "$ns.$directive"; - if (!empty($array["Null_$skey"])) { - $ret[$ns][$directive] = null; - continue; - } - if (!isset($array[$skey])) continue; - $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; - $ret[$ns][$directive] = $value; - } - return $ret; - } - - /** - * Loads configuration values from an ini file - * @param $filename Name of ini file - */ - public function loadIni($filename) { - if ($this->isFinalized('Cannot load directives after finalization')) return; - $array = parse_ini_file($filename, true); - $this->loadArray($array); - } - - /** - * Checks whether or not the configuration object is finalized. - * @param $error String error message, or false for no error - */ - public function isFinalized($error = false) { - if ($this->finalized && $error) { - $this->triggerError($error, E_USER_ERROR); - } - return $this->finalized; - } - - /** - * Finalizes configuration only if auto finalize is on and not - * already finalized - */ - public function autoFinalize() { - if ($this->autoFinalize) { - $this->finalize(); - } else { - $this->plist->squash(true); - } - } - - /** - * Finalizes a configuration object, prohibiting further change - */ - public function finalize() { - $this->finalized = true; - unset($this->parser); - } - - /** - * Produces a nicely formatted error message by supplying the - * stack frame information from two levels up and OUTSIDE of - * HTMLPurifier_Config. - */ - protected function triggerError($msg, $no) { - // determine previous stack frame - $backtrace = debug_backtrace(); - if ($this->chatty && isset($backtrace[1])) { - $frame = $backtrace[1]; - $extra = " on line {$frame['line']} in file {$frame['file']}"; - } else { - $extra = ''; - } - trigger_error($msg . $extra, $no); - } - - /** - * Returns a serialized form of the configuration object that can - * be reconstituted. - */ - public function serialize() { - $this->getDefinition('HTML'); - $this->getDefinition('CSS'); - $this->getDefinition('URI'); - return serialize($this); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php deleted file mode 100644 index b95aea18cc..0000000000 --- a/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php - -/** - * Fluent interface for validating the contents of member variables. - * This should be immutable. See HTMLPurifier_ConfigSchema_Validator for - * use-cases. We name this an 'atom' because it's ONLY for validations that - * are independent and usually scalar. - */ -class HTMLPurifier_ConfigSchema_ValidatorAtom -{ - - protected $context, $obj, $member, $contents; - - public function __construct($context, $obj, $member) { - $this->context = $context; - $this->obj = $obj; - $this->member = $member; - $this->contents =& $obj->$member; - } - - public function assertIsString() { - if (!is_string($this->contents)) $this->error('must be a string'); - return $this; - } - - public function assertIsBool() { - if (!is_bool($this->contents)) $this->error('must be a boolean'); - return $this; - } - - public function assertIsArray() { - if (!is_array($this->contents)) $this->error('must be an array'); - return $this; - } - - public function assertNotNull() { - if ($this->contents === null) $this->error('must not be null'); - return $this; - } - - public function assertAlnum() { - $this->assertIsString(); - if (!ctype_alnum($this->contents)) $this->error('must be alphanumeric'); - return $this; - } - - public function assertNotEmpty() { - if (empty($this->contents)) $this->error('must not be empty'); - return $this; - } - - public function assertIsLookup() { - $this->assertIsArray(); - foreach ($this->contents as $v) { - if ($v !== true) $this->error('must be a lookup array'); - } - return $this; - } - - protected function error($msg) { - throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema.ser b/library/HTMLPurifier/ConfigSchema/schema.ser deleted file mode 100644 index 22b8d54a59f17f73071055bc601d5a5f3a3f6b31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13244 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 Q8uHP_ug&>z-|yf52WIevWdHyG diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt deleted file mode 100644 index 888d558196..0000000000 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt +++ /dev/null @@ -1,18 +0,0 @@ -HTML.AllowedElements -TYPE: lookup/null -VERSION: 1.3.0 -DEFAULT: NULL ---DESCRIPTION-- -<p> - If HTML Purifier's tag set is unsatisfactory for your needs, you - can overload it with your own list of tags to allow. Note that this - method is subtractive: it does its job by taking away from HTML Purifier - usual feature set, so you cannot add a tag that HTML Purifier never - supported in the first place (like embed, form or head). If you - change this, you probably also want to change %HTML.AllowedAttributes. -</p> -<p> - <strong>Warning:</strong> If another directive conflicts with the - elements here, <em>that</em> directive will win and override. -</p> ---# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Context.php b/library/HTMLPurifier/Context.php deleted file mode 100644 index 9ddf0c5476..0000000000 --- a/library/HTMLPurifier/Context.php +++ /dev/null @@ -1,82 +0,0 @@ -<?php - -/** - * Registry object that contains information about the current context. - * @warning Is a bit buggy when variables are set to null: it thinks - * they don't exist! So use false instead, please. - * @note Since the variables Context deals with may not be objects, - * references are very important here! Do not remove! - */ -class HTMLPurifier_Context -{ - - /** - * Private array that stores the references. - */ - private $_storage = array(); - - /** - * Registers a variable into the context. - * @param $name String name - * @param $ref Reference to variable to be registered - */ - public function register($name, &$ref) { - if (isset($this->_storage[$name])) { - trigger_error("Name $name produces collision, cannot re-register", - E_USER_ERROR); - return; - } - $this->_storage[$name] =& $ref; - } - - /** - * Retrieves a variable reference from the context. - * @param $name String name - * @param $ignore_error Boolean whether or not to ignore error - */ - public function &get($name, $ignore_error = false) { - if (!isset($this->_storage[$name])) { - if (!$ignore_error) { - trigger_error("Attempted to retrieve non-existent variable $name", - E_USER_ERROR); - } - $var = null; // so we can return by reference - return $var; - } - return $this->_storage[$name]; - } - - /** - * Destorys a variable in the context. - * @param $name String name - */ - public function destroy($name) { - if (!isset($this->_storage[$name])) { - trigger_error("Attempted to destroy non-existent variable $name", - E_USER_ERROR); - return; - } - unset($this->_storage[$name]); - } - - /** - * Checks whether or not the variable exists. - * @param $name String name - */ - public function exists($name) { - return isset($this->_storage[$name]); - } - - /** - * Loads a series of variables from an associative array - * @param $context_array Assoc array of variables to load - */ - public function loadArray($context_array) { - foreach ($context_array as $key => $discard) { - $this->register($key, $context_array[$key]); - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Definition.php b/library/HTMLPurifier/Definition.php deleted file mode 100644 index a7408c9749..0000000000 --- a/library/HTMLPurifier/Definition.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -/** - * Super-class for definition datatype objects, implements serialization - * functions for the class. - */ -abstract class HTMLPurifier_Definition -{ - - /** - * Has setup() been called yet? - */ - public $setup = false; - - /** - * What type of definition is it? - */ - public $type; - - /** - * Sets up the definition object into the final form, something - * not done by the constructor - * @param $config HTMLPurifier_Config instance - */ - abstract protected function doSetup($config); - - /** - * Setup function that aborts if already setup - * @param $config HTMLPurifier_Config instance - */ - public function setup($config) { - if ($this->setup) return; - $this->setup = true; - $this->doSetup($config); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache/Decorator.php b/library/HTMLPurifier/DefinitionCache/Decorator.php deleted file mode 100644 index b0fb6d0cd6..0000000000 --- a/library/HTMLPurifier/DefinitionCache/Decorator.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache -{ - - /** - * Cache object we are decorating - */ - public $cache; - - public function __construct() {} - - /** - * Lazy decorator function - * @param $cache Reference to cache object to decorate - */ - public function decorate(&$cache) { - $decorator = $this->copy(); - // reference is necessary for mocks in PHP 4 - $decorator->cache =& $cache; - $decorator->type = $cache->type; - return $decorator; - } - - /** - * Cross-compatible clone substitute - */ - public function copy() { - return new HTMLPurifier_DefinitionCache_Decorator(); - } - - public function add($def, $config) { - return $this->cache->add($def, $config); - } - - public function set($def, $config) { - return $this->cache->set($def, $config); - } - - public function replace($def, $config) { - return $this->cache->replace($def, $config); - } - - public function get($config) { - return $this->cache->get($config); - } - - public function remove($config) { - return $this->cache->remove($config); - } - - public function flush($config) { - return $this->cache->flush($config); - } - - public function cleanup($config) { - return $this->cache->cleanup($config); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php deleted file mode 100644 index d4cc35c4bc..0000000000 --- a/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php - -/** - * Definition cache decorator class that cleans up the cache - * whenever there is a cache miss. - */ -class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends - HTMLPurifier_DefinitionCache_Decorator -{ - - public $name = 'Cleanup'; - - public function copy() { - return new HTMLPurifier_DefinitionCache_Decorator_Cleanup(); - } - - public function add($def, $config) { - $status = parent::add($def, $config); - if (!$status) parent::cleanup($config); - return $status; - } - - public function set($def, $config) { - $status = parent::set($def, $config); - if (!$status) parent::cleanup($config); - return $status; - } - - public function replace($def, $config) { - $status = parent::replace($def, $config); - if (!$status) parent::cleanup($config); - return $status; - } - - public function get($config) { - $ret = parent::get($config); - if (!$ret) parent::cleanup($config); - return $ret; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php b/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php deleted file mode 100644 index 18f16d32b6..0000000000 --- a/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php - -/** - * Definition cache decorator class that saves all cache retrievals - * to PHP's memory; good for unit tests or circumstances where - * there are lots of configuration objects floating around. - */ -class HTMLPurifier_DefinitionCache_Decorator_Memory extends - HTMLPurifier_DefinitionCache_Decorator -{ - - protected $definitions; - public $name = 'Memory'; - - public function copy() { - return new HTMLPurifier_DefinitionCache_Decorator_Memory(); - } - - public function add($def, $config) { - $status = parent::add($def, $config); - if ($status) $this->definitions[$this->generateKey($config)] = $def; - return $status; - } - - public function set($def, $config) { - $status = parent::set($def, $config); - if ($status) $this->definitions[$this->generateKey($config)] = $def; - return $status; - } - - public function replace($def, $config) { - $status = parent::replace($def, $config); - if ($status) $this->definitions[$this->generateKey($config)] = $def; - return $status; - } - - public function get($config) { - $key = $this->generateKey($config); - if (isset($this->definitions[$key])) return $this->definitions[$key]; - $this->definitions[$key] = parent::get($config); - return $this->definitions[$key]; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in deleted file mode 100644 index 21a8fcfda2..0000000000 --- a/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -require_once 'HTMLPurifier/DefinitionCache/Decorator.php'; - -/** - * Definition cache decorator template. - */ -class HTMLPurifier_DefinitionCache_Decorator_Template extends - HTMLPurifier_DefinitionCache_Decorator -{ - - var $name = 'Template'; // replace this - - function copy() { - // replace class name with yours - return new HTMLPurifier_DefinitionCache_Decorator_Template(); - } - - // remove methods you don't need - - function add($def, $config) { - return parent::add($def, $config); - } - - function set($def, $config) { - return parent::set($def, $config); - } - - function replace($def, $config) { - return parent::replace($def, $config); - } - - function get($config) { - return parent::get($config); - } - - function flush() { - return parent::flush(); - } - - function cleanup($config) { - return parent::cleanup($config); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache/Null.php b/library/HTMLPurifier/DefinitionCache/Null.php deleted file mode 100644 index 41d97e734f..0000000000 --- a/library/HTMLPurifier/DefinitionCache/Null.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -/** - * Null cache object to use when no caching is on. - */ -class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache -{ - - public function add($def, $config) { - return false; - } - - public function set($def, $config) { - return false; - } - - public function replace($def, $config) { - return false; - } - - public function remove($config) { - return false; - } - - public function get($config) { - return false; - } - - public function flush($config) { - return false; - } - - public function cleanup($config) { - return false; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache/Serializer.php b/library/HTMLPurifier/DefinitionCache/Serializer.php deleted file mode 100644 index 7a6aa93f02..0000000000 --- a/library/HTMLPurifier/DefinitionCache/Serializer.php +++ /dev/null @@ -1,172 +0,0 @@ -<?php - -class HTMLPurifier_DefinitionCache_Serializer extends - HTMLPurifier_DefinitionCache -{ - - public function add($def, $config) { - if (!$this->checkDefType($def)) return; - $file = $this->generateFilePath($config); - if (file_exists($file)) return false; - if (!$this->_prepareDir($config)) return false; - return $this->_write($file, serialize($def)); - } - - public function set($def, $config) { - if (!$this->checkDefType($def)) return; - $file = $this->generateFilePath($config); - if (!$this->_prepareDir($config)) return false; - return $this->_write($file, serialize($def)); - } - - public function replace($def, $config) { - if (!$this->checkDefType($def)) return; - $file = $this->generateFilePath($config); - if (!file_exists($file)) return false; - if (!$this->_prepareDir($config)) return false; - return $this->_write($file, serialize($def)); - } - - public function get($config) { - $file = $this->generateFilePath($config); - if (!file_exists($file)) return false; - return unserialize(file_get_contents($file)); - } - - public function remove($config) { - $file = $this->generateFilePath($config); - if (!file_exists($file)) return false; - return unlink($file); - } - - public function flush($config) { - if (!$this->_prepareDir($config)) return false; - $dir = $this->generateDirectoryPath($config); - $dh = opendir($dir); - while (false !== ($filename = readdir($dh))) { - if (empty($filename)) continue; - if ($filename[0] === '.') continue; - unlink($dir . '/' . $filename); - } - } - - public function cleanup($config) { - if (!$this->_prepareDir($config)) return false; - $dir = $this->generateDirectoryPath($config); - $dh = opendir($dir); - while (false !== ($filename = readdir($dh))) { - if (empty($filename)) continue; - if ($filename[0] === '.') continue; - $key = substr($filename, 0, strlen($filename) - 4); - if ($this->isOld($key, $config)) unlink($dir . '/' . $filename); - } - } - - /** - * Generates the file path to the serial file corresponding to - * the configuration and definition name - * @todo Make protected - */ - public function generateFilePath($config) { - $key = $this->generateKey($config); - return $this->generateDirectoryPath($config) . '/' . $key . '.ser'; - } - - /** - * Generates the path to the directory contain this cache's serial files - * @note No trailing slash - * @todo Make protected - */ - public function generateDirectoryPath($config) { - $base = $this->generateBaseDirectoryPath($config); - return $base . '/' . $this->type; - } - - /** - * Generates path to base directory that contains all definition type - * serials - * @todo Make protected - */ - public function generateBaseDirectoryPath($config) { - $base = $config->get('Cache.SerializerPath'); - $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base; - return $base; - } - - /** - * Convenience wrapper function for file_put_contents - * @param $file File name to write to - * @param $data Data to write into file - * @return Number of bytes written if success, or false if failure. - */ - private function _write($file, $data) { - return file_put_contents($file, $data); - } - - /** - * Prepares the directory that this type stores the serials in - * @return True if successful - */ - private function _prepareDir($config) { - $directory = $this->generateDirectoryPath($config); - if (!is_dir($directory)) { - $base = $this->generateBaseDirectoryPath($config); - if (!is_dir($base)) { - trigger_error('Base directory '.$base.' does not exist, - please create or change using %Cache.SerializerPath', - E_USER_WARNING); - return false; - } elseif (!$this->_testPermissions($base)) { - return false; - } - $old = umask(0022); // disable group and world writes - mkdir($directory); - umask($old); - } elseif (!$this->_testPermissions($directory)) { - return false; - } - return true; - } - - /** - * Tests permissions on a directory and throws out friendly - * error messages and attempts to chmod it itself if possible - */ - private function _testPermissions($dir) { - // early abort, if it is writable, everything is hunky-dory - if (is_writable($dir)) return true; - if (!is_dir($dir)) { - // generally, you'll want to handle this beforehand - // so a more specific error message can be given - trigger_error('Directory '.$dir.' does not exist', - E_USER_WARNING); - return false; - } - if (function_exists('posix_getuid')) { - // POSIX system, we can give more specific advice - if (fileowner($dir) === posix_getuid()) { - // we can chmod it ourselves - chmod($dir, 0755); - return true; - } elseif (filegroup($dir) === posix_getgid()) { - $chmod = '775'; - } else { - // PHP's probably running as nobody, so we'll - // need to give global permissions - $chmod = '777'; - } - trigger_error('Directory '.$dir.' not writable, '. - 'please chmod to ' . $chmod, - E_USER_WARNING); - } else { - // generic error message - trigger_error('Directory '.$dir.' not writable, '. - 'please alter file permissions', - E_USER_WARNING); - } - return false; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/EntityLookup/entities.ser b/library/HTMLPurifier/EntityLookup/entities.ser deleted file mode 100644 index f2b8b8f2db..0000000000 --- a/library/HTMLPurifier/EntityLookup/entities.ser +++ /dev/null @@ -1 +0,0 @@ -a:246:{s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"";s:3:"zwj";s:3:"";s:3:"lrm";s:3:"";s:3:"rlm";s:3:"";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";s:4:"fnof";s:2:"ƒ";s:5:"Alpha";s:2:"Α";s:4:"Beta";s:2:"Β";s:5:"Gamma";s:2:"Γ";s:5:"Delta";s:2:"Δ";s:7:"Epsilon";s:2:"Ε";s:4:"Zeta";s:2:"Ζ";s:3:"Eta";s:2:"Η";s:5:"Theta";s:2:"Θ";s:4:"Iota";s:2:"Ι";s:5:"Kappa";s:2:"Κ";s:6:"Lambda";s:2:"Λ";s:2:"Mu";s:2:"Μ";s:2:"Nu";s:2:"Ν";s:2:"Xi";s:2:"Ξ";s:7:"Omicron";s:2:"Ο";s:2:"Pi";s:2:"Π";s:3:"Rho";s:2:"Ρ";s:5:"Sigma";s:2:"Σ";s:3:"Tau";s:2:"Τ";s:7:"Upsilon";s:2:"Υ";s:3:"Phi";s:2:"Φ";s:3:"Chi";s:2:"Χ";s:3:"Psi";s:2:"Ψ";s:5:"Omega";s:2:"Ω";s:5:"alpha";s:2:"α";s:4:"beta";s:2:"β";s:5:"gamma";s:2:"γ";s:5:"delta";s:2:"δ";s:7:"epsilon";s:2:"ε";s:4:"zeta";s:2:"ζ";s:3:"eta";s:2:"η";s:5:"theta";s:2:"θ";s:4:"iota";s:2:"ι";s:5:"kappa";s:2:"κ";s:6:"lambda";s:2:"λ";s:2:"mu";s:2:"μ";s:2:"nu";s:2:"ν";s:2:"xi";s:2:"ξ";s:7:"omicron";s:2:"ο";s:2:"pi";s:2:"π";s:3:"rho";s:2:"ρ";s:6:"sigmaf";s:2:"ς";s:5:"sigma";s:2:"σ";s:3:"tau";s:2:"τ";s:7:"upsilon";s:2:"υ";s:3:"phi";s:2:"φ";s:3:"chi";s:2:"χ";s:3:"psi";s:2:"ψ";s:5:"omega";s:2:"ω";s:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";} \ No newline at end of file diff --git a/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/library/HTMLPurifier/Filter/ExtractStyleBlocks.php deleted file mode 100644 index bbf78a6630..0000000000 --- a/library/HTMLPurifier/Filter/ExtractStyleBlocks.php +++ /dev/null @@ -1,135 +0,0 @@ -<?php - -/** - * This filter extracts <style> blocks from input HTML, cleans them up - * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks') - * so they can be used elsewhere in the document. - * - * @note - * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for - * sample usage. - * - * @note - * This filter can also be used on stylesheets not included in the - * document--something purists would probably prefer. Just directly - * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS() - */ -class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter -{ - - public $name = 'ExtractStyleBlocks'; - private $_styleMatches = array(); - private $_tidy; - - public function __construct() { - $this->_tidy = new csstidy(); - } - - /** - * Save the contents of CSS blocks to style matches - * @param $matches preg_replace style $matches array - */ - protected function styleCallback($matches) { - $this->_styleMatches[] = $matches[1]; - } - - /** - * Removes inline <style> tags from HTML, saves them for later use - * @todo Extend to indicate non-text/css style blocks - */ - public function preFilter($html, $config, $context) { - $tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl'); - if ($tidy !== null) $this->_tidy = $tidy; - $html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html); - $style_blocks = $this->_styleMatches; - $this->_styleMatches = array(); // reset - $context->register('StyleBlocks', $style_blocks); // $context must not be reused - if ($this->_tidy) { - foreach ($style_blocks as &$style) { - $style = $this->cleanCSS($style, $config, $context); - } - } - return $html; - } - - /** - * Takes CSS (the stuff found in <style>) and cleans it. - * @warning Requires CSSTidy <http://csstidy.sourceforge.net/> - * @param $css CSS styling to clean - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @return Cleaned CSS - */ - public function cleanCSS($css, $config, $context) { - // prepare scope - $scope = $config->get('Filter.ExtractStyleBlocks.Scope'); - if ($scope !== null) { - $scopes = array_map('trim', explode(',', $scope)); - } else { - $scopes = array(); - } - // remove comments from CSS - $css = trim($css); - if (strncmp('<!--', $css, 4) === 0) { - $css = substr($css, 4); - } - if (strlen($css) > 3 && substr($css, -3) == '-->') { - $css = substr($css, 0, -3); - } - $css = trim($css); - $this->_tidy->parse($css); - $css_definition = $config->getDefinition('CSS'); - foreach ($this->_tidy->css as $k => $decls) { - // $decls are all CSS declarations inside an @ selector - $new_decls = array(); - foreach ($decls as $selector => $style) { - $selector = trim($selector); - if ($selector === '') continue; // should not happen - if ($selector[0] === '+') { - if ($selector !== '' && $selector[0] === '+') continue; - } - if (!empty($scopes)) { - $new_selector = array(); // because multiple ones are possible - $selectors = array_map('trim', explode(',', $selector)); - foreach ($scopes as $s1) { - foreach ($selectors as $s2) { - $new_selector[] = "$s1 $s2"; - } - } - $selector = implode(', ', $new_selector); // now it's a string - } - foreach ($style as $name => $value) { - if (!isset($css_definition->info[$name])) { - unset($style[$name]); - continue; - } - $def = $css_definition->info[$name]; - $ret = $def->validate($value, $config, $context); - if ($ret === false) unset($style[$name]); - else $style[$name] = $ret; - } - $new_decls[$selector] = $style; - } - $this->_tidy->css[$k] = $new_decls; - } - // remove stuff that shouldn't be used, could be reenabled - // after security risks are analyzed - $this->_tidy->import = array(); - $this->_tidy->charset = null; - $this->_tidy->namespace = null; - $css = $this->_tidy->print->plain(); - // we are going to escape any special characters <>& to ensure - // that no funny business occurs (i.e. </style> in a font-family prop). - if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { - $css = str_replace( - array('<', '>', '&'), - array('\3C ', '\3E ', '\26 '), - $css - ); - } - return $css; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Filter/YouTube.php b/library/HTMLPurifier/Filter/YouTube.php deleted file mode 100644 index 23df221eaa..0000000000 --- a/library/HTMLPurifier/Filter/YouTube.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter -{ - - public $name = 'YouTube'; - - public function preFilter($html, $config, $context) { - $pre_regex = '#<object[^>]+>.+?'. - 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?</object>#s'; - $pre_replace = '<span class="youtube-embed">\1</span>'; - return preg_replace($pre_regex, $pre_replace, $html); - } - - public function postFilter($html, $config, $context) { - $post_regex = '#<span class="youtube-embed">((?:v|cp)/[A-Za-z0-9\-_=]+)</span>#'; - return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); - } - - protected function armorUrl($url) { - return str_replace('--', '--', $url); - } - - protected function postFilterCallback($matches) { - $url = $this->armorUrl($matches[1]); - return '<object width="425" height="350" type="application/x-shockwave-flash" '. - 'data="http://www.youtube.com/'.$url.'">'. - '<param name="movie" value="http://www.youtube.com/'.$url.'"></param>'. - '<!--[if IE]>'. - '<embed src="http://www.youtube.com/'.$url.'"'. - 'type="application/x-shockwave-flash"'. - 'wmode="transparent" width="425" height="350" />'. - '<![endif]-->'. - '</object>'; - - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Forms.php b/library/HTMLPurifier/HTMLModule/Forms.php deleted file mode 100644 index 44c22f6f8b..0000000000 --- a/library/HTMLPurifier/HTMLModule/Forms.php +++ /dev/null @@ -1,118 +0,0 @@ -<?php - -/** - * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4. - */ -class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule -{ - public $name = 'Forms'; - public $safe = false; - - public $content_sets = array( - 'Block' => 'Form', - 'Inline' => 'Formctrl', - ); - - public function setup($config) { - $form = $this->addElement('form', 'Form', - 'Required: Heading | List | Block | fieldset', 'Common', array( - 'accept' => 'ContentTypes', - 'accept-charset' => 'Charsets', - 'action*' => 'URI', - 'method' => 'Enum#get,post', - // really ContentType, but these two are the only ones used today - 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data', - )); - $form->excludes = array('form' => true); - - $input = $this->addElement('input', 'Formctrl', 'Empty', 'Common', array( - 'accept' => 'ContentTypes', - 'accesskey' => 'Character', - 'alt' => 'Text', - 'checked' => 'Bool#checked', - 'disabled' => 'Bool#disabled', - 'maxlength' => 'Number', - 'name' => 'CDATA', - 'readonly' => 'Bool#readonly', - 'size' => 'Number', - 'src' => 'URI#embeds', - 'tabindex' => 'Number', - 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image', - 'value' => 'CDATA', - )); - $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input(); - - $this->addElement('select', 'Formctrl', 'Required: optgroup | option', 'Common', array( - 'disabled' => 'Bool#disabled', - 'multiple' => 'Bool#multiple', - 'name' => 'CDATA', - 'size' => 'Number', - 'tabindex' => 'Number', - )); - - $this->addElement('option', false, 'Optional: #PCDATA', 'Common', array( - 'disabled' => 'Bool#disabled', - 'label' => 'Text', - 'selected' => 'Bool#selected', - 'value' => 'CDATA', - )); - // It's illegal for there to be more than one selected, but not - // be multiple. Also, no selected means undefined behavior. This might - // be difficult to implement; perhaps an injector, or a context variable. - - $textarea = $this->addElement('textarea', 'Formctrl', 'Optional: #PCDATA', 'Common', array( - 'accesskey' => 'Character', - 'cols*' => 'Number', - 'disabled' => 'Bool#disabled', - 'name' => 'CDATA', - 'readonly' => 'Bool#readonly', - 'rows*' => 'Number', - 'tabindex' => 'Number', - )); - $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea(); - - $button = $this->addElement('button', 'Formctrl', 'Optional: #PCDATA | Heading | List | Block | Inline', 'Common', array( - 'accesskey' => 'Character', - 'disabled' => 'Bool#disabled', - 'name' => 'CDATA', - 'tabindex' => 'Number', - 'type' => 'Enum#button,submit,reset', - 'value' => 'CDATA', - )); - - // For exclusions, ideally we'd specify content sets, not literal elements - $button->excludes = $this->makeLookup( - 'form', 'fieldset', // Form - 'input', 'select', 'textarea', 'label', 'button', // Formctrl - 'a' // as per HTML 4.01 spec, this is omitted by modularization - ); - - // Extra exclusion: img usemap="" is not permitted within this element. - // We'll omit this for now, since we don't have any good way of - // indicating it yet. - - // This is HIGHLY user-unfriendly; we need a custom child-def for this - $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common'); - - $label = $this->addElement('label', 'Formctrl', 'Optional: #PCDATA | Inline', 'Common', array( - 'accesskey' => 'Character', - // 'for' => 'IDREF', // IDREF not implemented, cannot allow - )); - $label->excludes = array('label' => true); - - $this->addElement('legend', false, 'Optional: #PCDATA | Inline', 'Common', array( - 'accesskey' => 'Character', - )); - - $this->addElement('optgroup', false, 'Required: option', 'Common', array( - 'disabled' => 'Bool#disabled', - 'label*' => 'Text', - )); - - // Don't forget an injector for <isindex>. This one's a little complex - // because it maps to multiple elements. - - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Tidy/Strict.php b/library/HTMLPurifier/HTMLModule/Tidy/Strict.php deleted file mode 100644 index c73dc3c4d1..0000000000 --- a/library/HTMLPurifier/HTMLModule/Tidy/Strict.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php - -class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 -{ - public $name = 'Tidy_Strict'; - public $defaultLevel = 'light'; - - public function makeFixes() { - $r = parent::makeFixes(); - $r['blockquote#content_model_type'] = 'strictblockquote'; - return $r; - } - - public $defines_child_def = true; - public function getChildDef($def) { - if ($def->content_model_type != 'strictblockquote') return parent::getChildDef($def); - return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model); - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Injector/RemoveEmpty.php b/library/HTMLPurifier/Injector/RemoveEmpty.php deleted file mode 100644 index 638bfca03b..0000000000 --- a/library/HTMLPurifier/Injector/RemoveEmpty.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector -{ - - private $context, $config, $attrValidator, $removeNbsp, $removeNbspExceptions; - - public function prepare($config, $context) { - parent::prepare($config, $context); - $this->config = $config; - $this->context = $context; - $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp'); - $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions'); - $this->attrValidator = new HTMLPurifier_AttrValidator(); - } - - public function handleElement(&$token) { - if (!$token instanceof HTMLPurifier_Token_Start) return; - $next = false; - for ($i = $this->inputIndex + 1, $c = count($this->inputTokens); $i < $c; $i++) { - $next = $this->inputTokens[$i]; - if ($next instanceof HTMLPurifier_Token_Text) { - if ($next->is_whitespace) continue; - if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) { - $plain = str_replace("\xC2\xA0", "", $next->data); - $isWsOrNbsp = $plain === '' || ctype_space($plain); - if ($isWsOrNbsp) continue; - } - } - break; - } - if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) { - if ($token->name == 'colgroup') return; - $this->attrValidator->validateToken($token, $this->config, $this->context); - $token->armor['ValidateAttributes'] = true; - if (isset($token->attr['id']) || isset($token->attr['name'])) return; - $token = $i - $this->inputIndex + 1; - for ($b = $this->inputIndex - 1; $b > 0; $b--) { - $prev = $this->inputTokens[$b]; - if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) continue; - break; - } - // This is safe because we removed the token that triggered this. - $this->rewind($b - 1); - return; - } - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Language/messages/en.php b/library/HTMLPurifier/Language/messages/en.php deleted file mode 100644 index 8d7b5736bb..0000000000 --- a/library/HTMLPurifier/Language/messages/en.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -$fallback = false; - -$messages = array( - -'HTMLPurifier' => 'HTML Purifier', - -// for unit testing purposes -'LanguageFactoryTest: Pizza' => 'Pizza', -'LanguageTest: List' => '$1', -'LanguageTest: Hash' => '$1.Keys; $1.Values', - -'Item separator' => ', ', -'Item separator last' => ' and ', // non-Harvard style - -'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', -'ErrorCollector: At line' => ' at line $line', -'ErrorCollector: Incidental errors' => 'Incidental errors', - -'Lexer: Unclosed comment' => 'Unclosed comment', -'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', -'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', -'Lexer: Missing attribute key' => 'Attribute declaration has no key', -'Lexer: Missing end quote' => 'Attribute declaration has no end quote', -'Lexer: Extracted body' => 'Removed document metadata tags', - -'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized', -'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1', -'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text', -'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed', -'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed', -'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed', -'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end', -'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed', -'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens', - -'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed', -'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text', -'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact', -'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact', -'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed', -'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text', -'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized', -'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document', - -'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed', -'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element', -'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model', -'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed', - -'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys', -'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed', - -); - -$errorNames = array( - E_ERROR => 'Error', - E_WARNING => 'Warning', - E_NOTICE => 'Notice' -); - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Lexer/PEARSax3.php b/library/HTMLPurifier/Lexer/PEARSax3.php deleted file mode 100644 index 1d358c7b6b..0000000000 --- a/library/HTMLPurifier/Lexer/PEARSax3.php +++ /dev/null @@ -1,139 +0,0 @@ -<?php - -/** - * Proof-of-concept lexer that uses the PEAR package XML_HTMLSax3 to parse HTML. - * - * PEAR, not suprisingly, also has a SAX parser for HTML. I don't know - * very much about implementation, but it's fairly well written. However, that - * abstraction comes at a price: performance. You need to have it installed, - * and if the API changes, it might break our adapter. Not sure whether or not - * it's UTF-8 aware, but it has some entity parsing trouble (in all areas, - * text and attributes). - * - * Quite personally, I don't recommend using the PEAR class, and the defaults - * don't use it. The unit tests do perform the tests on the SAX parser too, but - * whatever it does for poorly formed HTML is up to it. - * - * @todo Generalize so that XML_HTMLSax is also supported. - * - * @warning Entity-resolution inside attributes is broken. - */ - -class HTMLPurifier_Lexer_PEARSax3 extends HTMLPurifier_Lexer -{ - - /** - * Internal accumulator array for SAX parsers. - */ - protected $tokens = array(); - protected $last_token_was_empty; - - private $parent_handler; - private $stack = array(); - - public function tokenizeHTML($string, $config, $context) { - - $this->tokens = array(); - $this->last_token_was_empty = false; - - $string = $this->normalize($string, $config, $context); - - $this->parent_handler = set_error_handler(array($this, 'muteStrictErrorHandler')); - - $parser = new XML_HTMLSax3(); - $parser->set_object($this); - $parser->set_element_handler('openHandler','closeHandler'); - $parser->set_data_handler('dataHandler'); - $parser->set_escape_handler('escapeHandler'); - - // doesn't seem to work correctly for attributes - $parser->set_option('XML_OPTION_ENTITIES_PARSED', 1); - - $parser->parse($string); - - restore_error_handler(); - - return $this->tokens; - - } - - /** - * Open tag event handler, interface is defined by PEAR package. - */ - public function openHandler(&$parser, $name, $attrs, $closed) { - // entities are not resolved in attrs - foreach ($attrs as $key => $attr) { - $attrs[$key] = $this->parseData($attr); - } - if ($closed) { - $this->tokens[] = new HTMLPurifier_Token_Empty($name, $attrs); - $this->last_token_was_empty = true; - } else { - $this->tokens[] = new HTMLPurifier_Token_Start($name, $attrs); - } - $this->stack[] = $name; - return true; - } - - /** - * Close tag event handler, interface is defined by PEAR package. - */ - public function closeHandler(&$parser, $name) { - // HTMLSax3 seems to always send empty tags an extra close tag - // check and ignore if you see it: - // [TESTME] to make sure it doesn't overreach - if ($this->last_token_was_empty) { - $this->last_token_was_empty = false; - return true; - } - $this->tokens[] = new HTMLPurifier_Token_End($name); - if (!empty($this->stack)) array_pop($this->stack); - return true; - } - - /** - * Data event handler, interface is defined by PEAR package. - */ - public function dataHandler(&$parser, $data) { - $this->last_token_was_empty = false; - $this->tokens[] = new HTMLPurifier_Token_Text($data); - return true; - } - - /** - * Escaped text handler, interface is defined by PEAR package. - */ - public function escapeHandler(&$parser, $data) { - if (strpos($data, '--') === 0) { - // remove trailing and leading double-dashes - $data = substr($data, 2); - if (strlen($data) >= 2 && substr($data, -2) == "--") { - $data = substr($data, 0, -2); - } - if (isset($this->stack[sizeof($this->stack) - 1]) && - $this->stack[sizeof($this->stack) - 1] == "style") { - $this->tokens[] = new HTMLPurifier_Token_Text($data); - } else { - $this->tokens[] = new HTMLPurifier_Token_Comment($data); - } - $this->last_token_was_empty = false; - } - // CDATA is handled elsewhere, but if it was handled here: - //if (strpos($data, '[CDATA[') === 0) { - // $this->tokens[] = new HTMLPurifier_Token_Text( - // substr($data, 7, strlen($data) - 9) ); - //} - return true; - } - - /** - * An error handler that mutes strict errors - */ - public function muteStrictErrorHandler($errno, $errstr, $errfile=null, $errline=null, $errcontext=null) { - if ($errno == E_STRICT) return; - return call_user_func($this->parent_handler, $errno, $errstr, $errfile, $errline, $errcontext); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Lexer/PH5P.php b/library/HTMLPurifier/Lexer/PH5P.php deleted file mode 100644 index fa1bf973e0..0000000000 --- a/library/HTMLPurifier/Lexer/PH5P.php +++ /dev/null @@ -1,3906 +0,0 @@ -<?php - -/** - * Experimental HTML5-based parser using Jeroen van der Meer's PH5P library. - * Occupies space in the HTML5 pseudo-namespace, which may cause conflicts. - * - * @note - * Recent changes to PHP's DOM extension have resulted in some fatal - * error conditions with the original version of PH5P. Pending changes, - * this lexer will punt to DirectLex if DOM throughs an exception. - */ - -class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex { - - public function tokenizeHTML($html, $config, $context) { - $new_html = $this->normalize($html, $config, $context); - $new_html = $this->wrapHTML($new_html, $config, $context); - try { - $parser = new HTML5($new_html); - $doc = $parser->save(); - } catch (DOMException $e) { - // Uh oh, it failed. Punt to DirectLex. - $lexer = new HTMLPurifier_Lexer_DirectLex(); - $context->register('PH5PError', $e); // save the error, so we can detect it - return $lexer->tokenizeHTML($html, $config, $context); // use original HTML - } - $tokens = array(); - $this->tokenizeDOM( - $doc->getElementsByTagName('html')->item(0)-> // <html> - getElementsByTagName('body')->item(0)-> // <body> - getElementsByTagName('div')->item(0) // <div> - , $tokens); - return $tokens; - } - -} - -/* - -Copyright 2007 Jeroen van der Meer <http://jero.net/> - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -class HTML5 { - private $data; - private $char; - private $EOF; - private $state; - private $tree; - private $token; - private $content_model; - private $escape = false; - private $entities = array('AElig;','AElig','AMP;','AMP','Aacute;','Aacute', - 'Acirc;','Acirc','Agrave;','Agrave','Alpha;','Aring;','Aring','Atilde;', - 'Atilde','Auml;','Auml','Beta;','COPY;','COPY','Ccedil;','Ccedil','Chi;', - 'Dagger;','Delta;','ETH;','ETH','Eacute;','Eacute','Ecirc;','Ecirc','Egrave;', - 'Egrave','Epsilon;','Eta;','Euml;','Euml','GT;','GT','Gamma;','Iacute;', - 'Iacute','Icirc;','Icirc','Igrave;','Igrave','Iota;','Iuml;','Iuml','Kappa;', - 'LT;','LT','Lambda;','Mu;','Ntilde;','Ntilde','Nu;','OElig;','Oacute;', - 'Oacute','Ocirc;','Ocirc','Ograve;','Ograve','Omega;','Omicron;','Oslash;', - 'Oslash','Otilde;','Otilde','Ouml;','Ouml','Phi;','Pi;','Prime;','Psi;', - 'QUOT;','QUOT','REG;','REG','Rho;','Scaron;','Sigma;','THORN;','THORN', - 'TRADE;','Tau;','Theta;','Uacute;','Uacute','Ucirc;','Ucirc','Ugrave;', - 'Ugrave','Upsilon;','Uuml;','Uuml','Xi;','Yacute;','Yacute','Yuml;','Zeta;', - 'aacute;','aacute','acirc;','acirc','acute;','acute','aelig;','aelig', - 'agrave;','agrave','alefsym;','alpha;','amp;','amp','and;','ang;','apos;', - 'aring;','aring','asymp;','atilde;','atilde','auml;','auml','bdquo;','beta;', - 'brvbar;','brvbar','bull;','cap;','ccedil;','ccedil','cedil;','cedil', - 'cent;','cent','chi;','circ;','clubs;','cong;','copy;','copy','crarr;', - 'cup;','curren;','curren','dArr;','dagger;','darr;','deg;','deg','delta;', - 'diams;','divide;','divide','eacute;','eacute','ecirc;','ecirc','egrave;', - 'egrave','empty;','emsp;','ensp;','epsilon;','equiv;','eta;','eth;','eth', - 'euml;','euml','euro;','exist;','fnof;','forall;','frac12;','frac12', - 'frac14;','frac14','frac34;','frac34','frasl;','gamma;','ge;','gt;','gt', - 'hArr;','harr;','hearts;','hellip;','iacute;','iacute','icirc;','icirc', - 'iexcl;','iexcl','igrave;','igrave','image;','infin;','int;','iota;', - 'iquest;','iquest','isin;','iuml;','iuml','kappa;','lArr;','lambda;','lang;', - 'laquo;','laquo','larr;','lceil;','ldquo;','le;','lfloor;','lowast;','loz;', - 'lrm;','lsaquo;','lsquo;','lt;','lt','macr;','macr','mdash;','micro;','micro', - 'middot;','middot','minus;','mu;','nabla;','nbsp;','nbsp','ndash;','ne;', - 'ni;','not;','not','notin;','nsub;','ntilde;','ntilde','nu;','oacute;', - 'oacute','ocirc;','ocirc','oelig;','ograve;','ograve','oline;','omega;', - 'omicron;','oplus;','or;','ordf;','ordf','ordm;','ordm','oslash;','oslash', - 'otilde;','otilde','otimes;','ouml;','ouml','para;','para','part;','permil;', - 'perp;','phi;','pi;','piv;','plusmn;','plusmn','pound;','pound','prime;', - 'prod;','prop;','psi;','quot;','quot','rArr;','radic;','rang;','raquo;', - 'raquo','rarr;','rceil;','rdquo;','real;','reg;','reg','rfloor;','rho;', - 'rlm;','rsaquo;','rsquo;','sbquo;','scaron;','sdot;','sect;','sect','shy;', - 'shy','sigma;','sigmaf;','sim;','spades;','sub;','sube;','sum;','sup1;', - 'sup1','sup2;','sup2','sup3;','sup3','sup;','supe;','szlig;','szlig','tau;', - 'there4;','theta;','thetasym;','thinsp;','thorn;','thorn','tilde;','times;', - 'times','trade;','uArr;','uacute;','uacute','uarr;','ucirc;','ucirc', - 'ugrave;','ugrave','uml;','uml','upsih;','upsilon;','uuml;','uuml','weierp;', - 'xi;','yacute;','yacute','yen;','yen','yuml;','yuml','zeta;','zwj;','zwnj;'); - - const PCDATA = 0; - const RCDATA = 1; - const CDATA = 2; - const PLAINTEXT = 3; - - const DOCTYPE = 0; - const STARTTAG = 1; - const ENDTAG = 2; - const COMMENT = 3; - const CHARACTR = 4; - const EOF = 5; - - public function __construct($data) { - $data = str_replace("\r\n", "\n", $data); - $data = str_replace("\r", null, $data); - - $this->data = $data; - $this->char = -1; - $this->EOF = strlen($data); - $this->tree = new HTML5TreeConstructer; - $this->content_model = self::PCDATA; - - $this->state = 'data'; - - while($this->state !== null) { - $this->{$this->state.'State'}(); - } - } - - public function save() { - return $this->tree->save(); - } - - private function char() { - return ($this->char < $this->EOF) - ? $this->data[$this->char] - : false; - } - - private function character($s, $l = 0) { - if($s + $l < $this->EOF) { - if($l === 0) { - return $this->data[$s]; - } else { - return substr($this->data, $s, $l); - } - } - } - - private function characters($char_class, $start) { - return preg_replace('#^(['.$char_class.']+).*#s', '\\1', substr($this->data, $start)); - } - - private function dataState() { - // Consume the next input character - $this->char++; - $char = $this->char(); - - if($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { - /* U+0026 AMPERSAND (&) - When the content model flag is set to one of the PCDATA or RCDATA - states: switch to the entity data state. Otherwise: treat it as per - the "anything else" entry below. */ - $this->state = 'entityData'; - - } elseif($char === '-') { - /* If the content model flag is set to either the RCDATA state or - the CDATA state, and the escape flag is false, and there are at - least three characters before this one in the input stream, and the - last four characters in the input stream, including this one, are - U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, - and U+002D HYPHEN-MINUS ("<!--"), then set the escape flag to true. */ - if(($this->content_model === self::RCDATA || $this->content_model === - self::CDATA) && $this->escape === false && - $this->char >= 3 && $this->character($this->char - 4, 4) === '<!--') { - $this->escape = true; - } - - /* In any case, emit the input character as a character token. Stay - in the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => $char - )); - - /* U+003C LESS-THAN SIGN (<) */ - } elseif($char === '<' && ($this->content_model === self::PCDATA || - (($this->content_model === self::RCDATA || - $this->content_model === self::CDATA) && $this->escape === false))) { - /* When the content model flag is set to the PCDATA state: switch - to the tag open state. - - When the content model flag is set to either the RCDATA state or - the CDATA state and the escape flag is false: switch to the tag - open state. - - Otherwise: treat it as per the "anything else" entry below. */ - $this->state = 'tagOpen'; - - /* U+003E GREATER-THAN SIGN (>) */ - } elseif($char === '>') { - /* If the content model flag is set to either the RCDATA state or - the CDATA state, and the escape flag is true, and the last three - characters in the input stream including this one are U+002D - HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"), - set the escape flag to false. */ - if(($this->content_model === self::RCDATA || - $this->content_model === self::CDATA) && $this->escape === true && - $this->character($this->char, 3) === '-->') { - $this->escape = false; - } - - /* In any case, emit the input character as a character token. - Stay in the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => $char - )); - - } elseif($this->char === $this->EOF) { - /* EOF - Emit an end-of-file token. */ - $this->EOF(); - - } elseif($this->content_model === self::PLAINTEXT) { - /* When the content model flag is set to the PLAINTEXT state - THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of - the text and emit it as a character token. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => substr($this->data, $this->char) - )); - - $this->EOF(); - - } else { - /* Anything else - THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that - otherwise would also be treated as a character token and emit it - as a single character token. Stay in the data state. */ - $len = strcspn($this->data, '<&', $this->char); - $char = substr($this->data, $this->char, $len); - $this->char += $len - 1; - - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => $char - )); - - $this->state = 'data'; - } - } - - private function entityDataState() { - // Attempt to consume an entity. - $entity = $this->entity(); - - // If nothing is returned, emit a U+0026 AMPERSAND character token. - // Otherwise, emit the character token that was returned. - $char = (!$entity) ? '&' : $entity; - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => $char - )); - - // Finally, switch to the data state. - $this->state = 'data'; - } - - private function tagOpenState() { - switch($this->content_model) { - case self::RCDATA: - case self::CDATA: - /* If the next input character is a U+002F SOLIDUS (/) character, - consume it and switch to the close tag open state. If the next - input character is not a U+002F SOLIDUS (/) character, emit a - U+003C LESS-THAN SIGN character token and switch to the data - state to process the next input character. */ - if($this->character($this->char + 1) === '/') { - $this->char++; - $this->state = 'closeTagOpen'; - - } else { - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '<' - )); - - $this->state = 'data'; - } - break; - - case self::PCDATA: - // If the content model flag is set to the PCDATA state - // Consume the next input character: - $this->char++; - $char = $this->char(); - - if($char === '!') { - /* U+0021 EXCLAMATION MARK (!) - Switch to the markup declaration open state. */ - $this->state = 'markupDeclarationOpen'; - - } elseif($char === '/') { - /* U+002F SOLIDUS (/) - Switch to the close tag open state. */ - $this->state = 'closeTagOpen'; - - } elseif(preg_match('/^[A-Za-z]$/', $char)) { - /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z - Create a new start tag token, set its tag name to the lowercase - version of the input character (add 0x0020 to the character's code - point), then switch to the tag name state. (Don't emit the token - yet; further details will be filled in before it is emitted.) */ - $this->token = array( - 'name' => strtolower($char), - 'type' => self::STARTTAG, - 'attr' => array() - ); - - $this->state = 'tagName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Parse error. Emit a U+003C LESS-THAN SIGN character token and a - U+003E GREATER-THAN SIGN character token. Switch to the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '<>' - )); - - $this->state = 'data'; - - } elseif($char === '?') { - /* U+003F QUESTION MARK (?) - Parse error. Switch to the bogus comment state. */ - $this->state = 'bogusComment'; - - } else { - /* Anything else - Parse error. Emit a U+003C LESS-THAN SIGN character token and - reconsume the current input character in the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '<' - )); - - $this->char--; - $this->state = 'data'; - } - break; - } - } - - private function closeTagOpenState() { - $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); - $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; - - if(($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && - (!$the_same || ($the_same && (!preg_match('/[\t\n\x0b\x0c >\/]/', - $this->character($this->char + 1 + strlen($next_node))) || $this->EOF === $this->char)))) { - /* If the content model flag is set to the RCDATA or CDATA states then - examine the next few characters. If they do not match the tag name of - the last start tag token emitted (case insensitively), or if they do but - they are not immediately followed by one of the following characters: - * U+0009 CHARACTER TABULATION - * U+000A LINE FEED (LF) - * U+000B LINE TABULATION - * U+000C FORM FEED (FF) - * U+0020 SPACE - * U+003E GREATER-THAN SIGN (>) - * U+002F SOLIDUS (/) - * EOF - ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character - token, a U+002F SOLIDUS character token, and switch to the data state - to process the next input character. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '</' - )); - - $this->state = 'data'; - - } else { - /* Otherwise, if the content model flag is set to the PCDATA state, - or if the next few characters do match that tag name, consume the - next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[A-Za-z]$/', $char)) { - /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z - Create a new end tag token, set its tag name to the lowercase version - of the input character (add 0x0020 to the character's code point), then - switch to the tag name state. (Don't emit the token yet; further details - will be filled in before it is emitted.) */ - $this->token = array( - 'name' => strtolower($char), - 'type' => self::ENDTAG - ); - - $this->state = 'tagName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Parse error. Switch to the data state. */ - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F - SOLIDUS character token. Reconsume the EOF character in the data state. */ - $this->emitToken(array( - 'type' => self::CHARACTR, - 'data' => '</' - )); - - $this->char--; - $this->state = 'data'; - - } else { - /* Parse error. Switch to the bogus comment state. */ - $this->state = 'bogusComment'; - } - } - } - - private function tagNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } elseif($char === '/') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Switch to the before - attribute name state. */ - $this->state = 'beforeAttributeName'; - - } else { - /* Anything else - Append the current input character to the current tag token's tag name. - Stay in the tag name state. */ - $this->token['name'] .= strtolower($char); - $this->state = 'tagName'; - } - } - - private function beforeAttributeNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '/') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Stay in the before - attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Start a new attribute in the current tag token. Set that attribute's - name to the current input character, and its value to the empty string. - Switch to the attribute name state. */ - $this->token['attr'][] = array( - 'name' => strtolower($char), - 'value' => null - ); - - $this->state = 'attributeName'; - } - } - - private function attributeNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the before attribute name state. */ - $this->state = 'afterAttributeName'; - - } elseif($char === '=') { - /* U+003D EQUALS SIGN (=) - Switch to the before attribute value state. */ - $this->state = 'beforeAttributeValue'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '/' && $this->character($this->char + 1) !== '>') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Switch to the before - attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's name. - Stay in the attribute name state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['name'] .= strtolower($char); - - $this->state = 'attributeName'; - } - } - - private function afterAttributeNameState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the after attribute name state. */ - $this->state = 'afterAttributeName'; - - } elseif($char === '=') { - /* U+003D EQUALS SIGN (=) - Switch to the before attribute value state. */ - $this->state = 'beforeAttributeValue'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '/' && $this->character($this->char + 1) !== '>') { - /* U+002F SOLIDUS (/) - Parse error unless this is a permitted slash. Switch to the - before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the EOF - character in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Start a new attribute in the current tag token. Set that attribute's - name to the current input character, and its value to the empty string. - Switch to the attribute name state. */ - $this->token['attr'][] = array( - 'name' => strtolower($char), - 'value' => null - ); - - $this->state = 'attributeName'; - } - } - - private function beforeAttributeValueState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Stay in the before attribute value state. */ - $this->state = 'beforeAttributeValue'; - - } elseif($char === '"') { - /* U+0022 QUOTATION MARK (") - Switch to the attribute value (double-quoted) state. */ - $this->state = 'attributeValueDoubleQuoted'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the attribute value (unquoted) state and reconsume - this input character. */ - $this->char--; - $this->state = 'attributeValueUnquoted'; - - } elseif($char === '\'') { - /* U+0027 APOSTROPHE (') - Switch to the attribute value (single-quoted) state. */ - $this->state = 'attributeValueSingleQuoted'; - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Switch to the attribute value (unquoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueUnquoted'; - } - } - - private function attributeValueDoubleQuotedState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if($char === '"') { - /* U+0022 QUOTATION MARK (") - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the entity in attribute value state. */ - $this->entityInAttributeValueState('double'); - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the character - in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Stay in the attribute value (double-quoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueDoubleQuoted'; - } - } - - private function attributeValueSingleQuotedState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if($char === '\'') { - /* U+0022 QUOTATION MARK (') - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the entity in attribute value state. */ - $this->entityInAttributeValueState('single'); - - } elseif($this->char === $this->EOF) { - /* EOF - Parse error. Emit the current tag token. Reconsume the character - in the data state. */ - $this->emitToken($this->token); - - $this->char--; - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Stay in the attribute value (single-quoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueSingleQuoted'; - } - } - - private function attributeValueUnquotedState() { - // Consume the next input character: - $this->char++; - $char = $this->character($this->char); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - /* U+0009 CHARACTER TABULATION - U+000A LINE FEED (LF) - U+000B LINE TABULATION - U+000C FORM FEED (FF) - U+0020 SPACE - Switch to the before attribute name state. */ - $this->state = 'beforeAttributeName'; - - } elseif($char === '&') { - /* U+0026 AMPERSAND (&) - Switch to the entity in attribute value state. */ - $this->entityInAttributeValueState(); - - } elseif($char === '>') { - /* U+003E GREATER-THAN SIGN (>) - Emit the current tag token. Switch to the data state. */ - $this->emitToken($this->token); - $this->state = 'data'; - - } else { - /* Anything else - Append the current input character to the current attribute's value. - Stay in the attribute value (unquoted) state. */ - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - - $this->state = 'attributeValueUnquoted'; - } - } - - private function entityInAttributeValueState() { - // Attempt to consume an entity. - $entity = $this->entity(); - - // If nothing is returned, append a U+0026 AMPERSAND character to the - // current attribute's value. Otherwise, emit the character token that - // was returned. - $char = (!$entity) - ? '&' - : $entity; - - $last = count($this->token['attr']) - 1; - $this->token['attr'][$last]['value'] .= $char; - } - - private function bogusCommentState() { - /* Consume every character up to the first U+003E GREATER-THAN SIGN - character (>) or the end of the file (EOF), whichever comes first. Emit - a comment token whose data is the concatenation of all the characters - starting from and including the character that caused the state machine - to switch into the bogus comment state, up to and including the last - consumed character before the U+003E character, if any, or up to the - end of the file otherwise. (If the comment was started by the end of - the file (EOF), the token is empty.) */ - $data = $this->characters('^>', $this->char); - $this->emitToken(array( - 'data' => $data, - 'type' => self::COMMENT - )); - - $this->char += strlen($data); - - /* Switch to the data state. */ - $this->state = 'data'; - - /* If the end of the file was reached, reconsume the EOF character. */ - if($this->char === $this->EOF) { - $this->char = $this->EOF - 1; - } - } - - private function markupDeclarationOpenState() { - /* If the next two characters are both U+002D HYPHEN-MINUS (-) - characters, consume those two characters, create a comment token whose - data is the empty string, and switch to the comment state. */ - if($this->character($this->char + 1, 2) === '--') { - $this->char += 2; - $this->state = 'comment'; - $this->token = array( - 'data' => null, - 'type' => self::COMMENT - ); - - /* Otherwise if the next seven chacacters are a case-insensitive match - for the word "DOCTYPE", then consume those characters and switch to the - DOCTYPE state. */ - } elseif(strtolower($this->character($this->char + 1, 7)) === 'doctype') { - $this->char += 7; - $this->state = 'doctype'; - - /* Otherwise, is is a parse error. Switch to the bogus comment state. - The next character that is consumed, if any, is the first character - that will be in the comment. */ - } else { - $this->char++; - $this->state = 'bogusComment'; - } - } - - private function commentState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - /* U+002D HYPHEN-MINUS (-) */ - if($char === '-') { - /* Switch to the comment dash state */ - $this->state = 'commentDash'; - - /* EOF */ - } elseif($this->char === $this->EOF) { - /* Parse error. Emit the comment token. Reconsume the EOF character - in the data state. */ - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - /* Anything else */ - } else { - /* Append the input character to the comment token's data. Stay in - the comment state. */ - $this->token['data'] .= $char; - } - } - - private function commentDashState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - /* U+002D HYPHEN-MINUS (-) */ - if($char === '-') { - /* Switch to the comment end state */ - $this->state = 'commentEnd'; - - /* EOF */ - } elseif($this->char === $this->EOF) { - /* Parse error. Emit the comment token. Reconsume the EOF character - in the data state. */ - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - /* Anything else */ - } else { - /* Append a U+002D HYPHEN-MINUS (-) character and the input - character to the comment token's data. Switch to the comment state. */ - $this->token['data'] .= '-'.$char; - $this->state = 'comment'; - } - } - - private function commentEndState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($char === '-') { - $this->token['data'] .= '-'; - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - $this->token['data'] .= '--'.$char; - $this->state = 'comment'; - } - } - - private function doctypeState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - $this->state = 'beforeDoctypeName'; - - } else { - $this->char--; - $this->state = 'beforeDoctypeName'; - } - } - - private function beforeDoctypeNameState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - // Stay in the before DOCTYPE name state. - - } elseif(preg_match('/^[a-z]$/', $char)) { - $this->token = array( - 'name' => strtoupper($char), - 'type' => self::DOCTYPE, - 'error' => true - ); - - $this->state = 'doctypeName'; - - } elseif($char === '>') { - $this->emitToken(array( - 'name' => null, - 'type' => self::DOCTYPE, - 'error' => true - )); - - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - $this->emitToken(array( - 'name' => null, - 'type' => self::DOCTYPE, - 'error' => true - )); - - $this->char--; - $this->state = 'data'; - - } else { - $this->token = array( - 'name' => $char, - 'type' => self::DOCTYPE, - 'error' => true - ); - - $this->state = 'doctypeName'; - } - } - - private function doctypeNameState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - $this->state = 'AfterDoctypeName'; - - } elseif($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif(preg_match('/^[a-z]$/', $char)) { - $this->token['name'] .= strtoupper($char); - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - $this->token['name'] .= $char; - } - - $this->token['error'] = ($this->token['name'] === 'HTML') - ? false - : true; - } - - private function afterDoctypeNameState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { - // Stay in the DOCTYPE name state. - - } elseif($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - $this->token['error'] = true; - $this->state = 'bogusDoctype'; - } - } - - private function bogusDoctypeState() { - /* Consume the next input character: */ - $this->char++; - $char = $this->char(); - - if($char === '>') { - $this->emitToken($this->token); - $this->state = 'data'; - - } elseif($this->char === $this->EOF) { - $this->emitToken($this->token); - $this->char--; - $this->state = 'data'; - - } else { - // Stay in the bogus DOCTYPE state. - } - } - - private function entity() { - $start = $this->char; - - // This section defines how to consume an entity. This definition is - // used when parsing entities in text and in attributes. - - // The behaviour depends on the identity of the next character (the - // one immediately after the U+0026 AMPERSAND character): - - switch($this->character($this->char + 1)) { - // U+0023 NUMBER SIGN (#) - case '#': - - // The behaviour further depends on the character after the - // U+0023 NUMBER SIGN: - switch($this->character($this->char + 1)) { - // U+0078 LATIN SMALL LETTER X - // U+0058 LATIN CAPITAL LETTER X - case 'x': - case 'X': - // Follow the steps below, but using the range of - // characters U+0030 DIGIT ZERO through to U+0039 DIGIT - // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 - // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER - // A, through to U+0046 LATIN CAPITAL LETTER F (in other - // words, 0-9, A-F, a-f). - $char = 1; - $char_class = '0-9A-Fa-f'; - break; - - // Anything else - default: - // Follow the steps below, but using the range of - // characters U+0030 DIGIT ZERO through to U+0039 DIGIT - // NINE (i.e. just 0-9). - $char = 0; - $char_class = '0-9'; - break; - } - - // Consume as many characters as match the range of characters - // given above. - $this->char++; - $e_name = $this->characters($char_class, $this->char + $char + 1); - $entity = $this->character($start, $this->char); - $cond = strlen($e_name) > 0; - - // The rest of the parsing happens bellow. - break; - - // Anything else - default: - // Consume the maximum number of characters possible, with the - // consumed characters case-sensitively matching one of the - // identifiers in the first column of the entities table. - $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); - $len = strlen($e_name); - - for($c = 1; $c <= $len; $c++) { - $id = substr($e_name, 0, $c); - $this->char++; - - if(in_array($id, $this->entities)) { - if ($e_name[$c-1] !== ';') { - if ($c < $len && $e_name[$c] == ';') { - $this->char++; // consume extra semicolon - } - } - $entity = $id; - break; - } - } - - $cond = isset($entity); - // The rest of the parsing happens bellow. - break; - } - - if(!$cond) { - // If no match can be made, then this is a parse error. No - // characters are consumed, and nothing is returned. - $this->char = $start; - return false; - } - - // Return a character token for the character corresponding to the - // entity name (as given by the second column of the entities table). - return html_entity_decode('&'.$entity.';', ENT_QUOTES, 'UTF-8'); - } - - private function emitToken($token) { - $emit = $this->tree->emitToken($token); - - if(is_int($emit)) { - $this->content_model = $emit; - - } elseif($token['type'] === self::ENDTAG) { - $this->content_model = self::PCDATA; - } - } - - private function EOF() { - $this->state = null; - $this->tree->emitToken(array( - 'type' => self::EOF - )); - } -} - -class HTML5TreeConstructer { - public $stack = array(); - - private $phase; - private $mode; - private $dom; - private $foster_parent = null; - private $a_formatting = array(); - - private $head_pointer = null; - private $form_pointer = null; - - private $scoping = array('button','caption','html','marquee','object','table','td','th'); - private $formatting = array('a','b','big','em','font','i','nobr','s','small','strike','strong','tt','u'); - private $special = array('address','area','base','basefont','bgsound', - 'blockquote','body','br','center','col','colgroup','dd','dir','div','dl', - 'dt','embed','fieldset','form','frame','frameset','h1','h2','h3','h4','h5', - 'h6','head','hr','iframe','image','img','input','isindex','li','link', - 'listing','menu','meta','noembed','noframes','noscript','ol','optgroup', - 'option','p','param','plaintext','pre','script','select','spacer','style', - 'tbody','textarea','tfoot','thead','title','tr','ul','wbr'); - - // The different phases. - const INIT_PHASE = 0; - const ROOT_PHASE = 1; - const MAIN_PHASE = 2; - const END_PHASE = 3; - - // The different insertion modes for the main phase. - const BEFOR_HEAD = 0; - const IN_HEAD = 1; - const AFTER_HEAD = 2; - const IN_BODY = 3; - const IN_TABLE = 4; - const IN_CAPTION = 5; - const IN_CGROUP = 6; - const IN_TBODY = 7; - const IN_ROW = 8; - const IN_CELL = 9; - const IN_SELECT = 10; - const AFTER_BODY = 11; - const IN_FRAME = 12; - const AFTR_FRAME = 13; - - // The different types of elements. - const SPECIAL = 0; - const SCOPING = 1; - const FORMATTING = 2; - const PHRASING = 3; - - const MARKER = 0; - - public function __construct() { - $this->phase = self::INIT_PHASE; - $this->mode = self::BEFOR_HEAD; - $this->dom = new DOMDocument; - - $this->dom->encoding = 'UTF-8'; - $this->dom->preserveWhiteSpace = true; - $this->dom->substituteEntities = true; - $this->dom->strictErrorChecking = false; - } - - // Process tag tokens - public function emitToken($token) { - switch($this->phase) { - case self::INIT_PHASE: return $this->initPhase($token); break; - case self::ROOT_PHASE: return $this->rootElementPhase($token); break; - case self::MAIN_PHASE: return $this->mainPhase($token); break; - case self::END_PHASE : return $this->trailingEndPhase($token); break; - } - } - - private function initPhase($token) { - /* Initially, the tree construction stage must handle each token - emitted from the tokenisation stage as follows: */ - - /* A DOCTYPE token that is marked as being in error - A comment token - A start tag token - An end tag token - A character token that is not one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE - An end-of-file token */ - if((isset($token['error']) && $token['error']) || - $token['type'] === HTML5::COMMENT || - $token['type'] === HTML5::STARTTAG || - $token['type'] === HTML5::ENDTAG || - $token['type'] === HTML5::EOF || - ($token['type'] === HTML5::CHARACTR && isset($token['data']) && - !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))) { - /* This specification does not define how to handle this case. In - particular, user agents may ignore the entirety of this specification - altogether for such documents, and instead invoke special parse modes - with a greater emphasis on backwards compatibility. */ - - $this->phase = self::ROOT_PHASE; - return $this->rootElementPhase($token); - - /* A DOCTYPE token marked as being correct */ - } elseif(isset($token['error']) && !$token['error']) { - /* Append a DocumentType node to the Document node, with the name - attribute set to the name given in the DOCTYPE token (which will be - "HTML"), and the other attributes specific to DocumentType objects - set to null, empty lists, or the empty string as appropriate. */ - $doctype = new DOMDocumentType(null, null, 'HTML'); - - /* Then, switch to the root element phase of the tree construction - stage. */ - $this->phase = self::ROOT_PHASE; - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - } elseif(isset($token['data']) && preg_match('/^[\t\n\x0b\x0c ]+$/', - $token['data'])) { - /* Append that character to the Document node. */ - $text = $this->dom->createTextNode($token['data']); - $this->dom->appendChild($text); - } - } - - private function rootElementPhase($token) { - /* After the initial phase, as each token is emitted from the tokenisation - stage, it must be processed as described in this section. */ - - /* A DOCTYPE token */ - if($token['type'] === HTML5::DOCTYPE) { - // Parse error. Ignore the token. - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the Document object with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - $this->dom->appendChild($comment); - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - } elseif($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append that character to the Document node. */ - $text = $this->dom->createTextNode($token['data']); - $this->dom->appendChild($text); - - /* A character token that is not one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED - (FF), or U+0020 SPACE - A start tag token - An end tag token - An end-of-file token */ - } elseif(($token['type'] === HTML5::CHARACTR && - !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || - $token['type'] === HTML5::STARTTAG || - $token['type'] === HTML5::ENDTAG || - $token['type'] === HTML5::EOF) { - /* Create an HTMLElement node with the tag name html, in the HTML - namespace. Append it to the Document object. Switch to the main - phase and reprocess the current token. */ - $html = $this->dom->createElement('html'); - $this->dom->appendChild($html); - $this->stack[] = $html; - - $this->phase = self::MAIN_PHASE; - return $this->mainPhase($token); - } - } - - private function mainPhase($token) { - /* Tokens in the main phase must be handled as follows: */ - - /* A DOCTYPE token */ - if($token['type'] === HTML5::DOCTYPE) { - // Parse error. Ignore the token. - - /* A start tag token with the tag name "html" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { - /* If this start tag token was not the first start tag token, then - it is a parse error. */ - - /* For each attribute on the token, check to see if the attribute - is already present on the top element of the stack of open elements. - If it is not, add the attribute and its corresponding value to that - element. */ - foreach($token['attr'] as $attr) { - if(!$this->stack[0]->hasAttribute($attr['name'])) { - $this->stack[0]->setAttribute($attr['name'], $attr['value']); - } - } - - /* An end-of-file token */ - } elseif($token['type'] === HTML5::EOF) { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* Anything else. */ - } else { - /* Depends on the insertion mode: */ - switch($this->mode) { - case self::BEFOR_HEAD: return $this->beforeHead($token); break; - case self::IN_HEAD: return $this->inHead($token); break; - case self::AFTER_HEAD: return $this->afterHead($token); break; - case self::IN_BODY: return $this->inBody($token); break; - case self::IN_TABLE: return $this->inTable($token); break; - case self::IN_CAPTION: return $this->inCaption($token); break; - case self::IN_CGROUP: return $this->inColumnGroup($token); break; - case self::IN_TBODY: return $this->inTableBody($token); break; - case self::IN_ROW: return $this->inRow($token); break; - case self::IN_CELL: return $this->inCell($token); break; - case self::IN_SELECT: return $this->inSelect($token); break; - case self::AFTER_BODY: return $this->afterBody($token); break; - case self::IN_FRAME: return $this->inFrameset($token); break; - case self::AFTR_FRAME: return $this->afterFrameset($token); break; - case self::END_PHASE: return $this->trailingEndPhase($token); break; - } - } - } - - private function beforeHead($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data attribute - set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag token with the tag name "head" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { - /* Create an element for the token, append the new element to the - current node and push it onto the stack of open elements. */ - $element = $this->insertElement($token); - - /* Set the head element pointer to this new element node. */ - $this->head_pointer = $element; - - /* Change the insertion mode to "in head". */ - $this->mode = self::IN_HEAD; - - /* A start tag token whose tag name is one of: "base", "link", "meta", - "script", "style", "title". Or an end tag with the tag name "html". - Or a character token that is not one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE. Or any other start tag token */ - } elseif($token['type'] === HTML5::STARTTAG || - ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || - ($token['type'] === HTML5::CHARACTR && !preg_match('/^[\t\n\x0b\x0c ]$/', - $token['data']))) { - /* Act as if a start tag token with the tag name "head" and no - attributes had been seen, then reprocess the current token. */ - $this->beforeHead(array( - 'name' => 'head', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inHead($token); - - /* Any other end tag */ - } elseif($token['type'] === HTML5::ENDTAG) { - /* Parse error. Ignore the token. */ - } - } - - private function inHead($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE. - - THIS DIFFERS FROM THE SPEC: If the current node is either a title, style - or script element, append the character to the current node regardless - of its content. */ - if(($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( - $token['type'] === HTML5::CHARACTR && in_array(end($this->stack)->nodeName, - array('title', 'style', 'script')))) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data attribute - set to the data given in the comment token. */ - $this->insertComment($token['data']); - - } elseif($token['type'] === HTML5::ENDTAG && - in_array($token['name'], array('title', 'style', 'script'))) { - array_pop($this->stack); - return HTML5::PCDATA; - - /* A start tag with the tag name "title" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { - /* Create an element for the token and append the new element to the - node pointed to by the head element pointer, or, if that is null - (innerHTML case), to the current node. */ - if($this->head_pointer !== null) { - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - - } else { - $element = $this->insertElement($token); - } - - /* Switch the tokeniser's content model flag to the RCDATA state. */ - return HTML5::RCDATA; - - /* A start tag with the tag name "style" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { - /* Create an element for the token and append the new element to the - node pointed to by the head element pointer, or, if that is null - (innerHTML case), to the current node. */ - if($this->head_pointer !== null) { - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - - } else { - $this->insertElement($token); - } - - /* Switch the tokeniser's content model flag to the CDATA state. */ - return HTML5::CDATA; - - /* A start tag with the tag name "script" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { - /* Create an element for the token. */ - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - - /* Switch the tokeniser's content model flag to the CDATA state. */ - return HTML5::CDATA; - - /* A start tag with the tag name "base", "link", or "meta" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('base', 'link', 'meta'))) { - /* Create an element for the token and append the new element to the - node pointed to by the head element pointer, or, if that is null - (innerHTML case), to the current node. */ - if($this->head_pointer !== null) { - $element = $this->insertElement($token, false); - $this->head_pointer->appendChild($element); - array_pop($this->stack); - - } else { - $this->insertElement($token); - } - - /* An end tag with the tag name "head" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { - /* If the current node is a head element, pop the current node off - the stack of open elements. */ - if($this->head_pointer->isSameNode(end($this->stack))) { - array_pop($this->stack); - - /* Otherwise, this is a parse error. */ - } else { - // k - } - - /* Change the insertion mode to "after head". */ - $this->mode = self::AFTER_HEAD; - - /* A start tag with the tag name "head" or an end tag except "html". */ - } elseif(($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || - ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')) { - // Parse error. Ignore the token. - - /* Anything else */ - } else { - /* If the current node is a head element, act as if an end tag - token with the tag name "head" had been seen. */ - if($this->head_pointer->isSameNode(end($this->stack))) { - $this->inHead(array( - 'name' => 'head', - 'type' => HTML5::ENDTAG - )); - - /* Otherwise, change the insertion mode to "after head". */ - } else { - $this->mode = self::AFTER_HEAD; - } - - /* Then, reprocess the current token. */ - return $this->afterHead($token); - } - } - - private function afterHead($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data attribute - set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag token with the tag name "body" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { - /* Insert a body element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in body". */ - $this->mode = self::IN_BODY; - - /* A start tag token with the tag name "frameset" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { - /* Insert a frameset element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in frameset". */ - $this->mode = self::IN_FRAME; - - /* A start tag token whose tag name is one of: "base", "link", "meta", - "script", "style", "title" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('base', 'link', 'meta', 'script', 'style', 'title'))) { - /* Parse error. Switch the insertion mode back to "in head" and - reprocess the token. */ - $this->mode = self::IN_HEAD; - return $this->inHead($token); - - /* Anything else */ - } else { - /* Act as if a start tag token with the tag name "body" and no - attributes had been seen, and then reprocess the current token. */ - $this->afterHead(array( - 'name' => 'body', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inBody($token); - } - } - - private function inBody($token) { - /* Handle the token as follows: */ - - switch($token['type']) { - /* A character token */ - case HTML5::CHARACTR: - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Append the token's character to the current node. */ - $this->insertText($token['data']); - break; - - /* A comment token */ - case HTML5::COMMENT: - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - break; - - case HTML5::STARTTAG: - switch($token['name']) { - /* A start tag token whose tag name is one of: "script", - "style" */ - case 'script': case 'style': - /* Process the token as if the insertion mode had been "in - head". */ - return $this->inHead($token); - break; - - /* A start tag token whose tag name is one of: "base", "link", - "meta", "title" */ - case 'base': case 'link': case 'meta': case 'title': - /* Parse error. Process the token as if the insertion mode - had been "in head". */ - return $this->inHead($token); - break; - - /* A start tag token with the tag name "body" */ - case 'body': - /* Parse error. If the second element on the stack of open - elements is not a body element, or, if the stack of open - elements has only one node on it, then ignore the token. - (innerHTML case) */ - if(count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { - // Ignore - - /* Otherwise, for each attribute on the token, check to see - if the attribute is already present on the body element (the - second element) on the stack of open elements. If it is not, - add the attribute and its corresponding value to that - element. */ - } else { - foreach($token['attr'] as $attr) { - if(!$this->stack[1]->hasAttribute($attr['name'])) { - $this->stack[1]->setAttribute($attr['name'], $attr['value']); - } - } - } - break; - - /* A start tag whose tag name is one of: "address", - "blockquote", "center", "dir", "div", "dl", "fieldset", - "listing", "menu", "ol", "p", "ul" */ - case 'address': case 'blockquote': case 'center': case 'dir': - case 'div': case 'dl': case 'fieldset': case 'listing': - case 'menu': case 'ol': case 'p': case 'ul': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been - seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - break; - - /* A start tag whose tag name is "form" */ - case 'form': - /* If the form element pointer is not null, ignore the - token with a parse error. */ - if($this->form_pointer !== null) { - // Ignore. - - /* Otherwise: */ - } else { - /* If the stack of open elements has a p element in - scope, then act as if an end tag with the tag name p - had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token, and set the - form element pointer to point to the element created. */ - $element = $this->insertElement($token); - $this->form_pointer = $element; - } - break; - - /* A start tag whose tag name is "li", "dd" or "dt" */ - case 'li': case 'dd': case 'dt': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been - seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - $stack_length = count($this->stack) - 1; - - for($n = $stack_length; 0 <= $n; $n--) { - /* 1. Initialise node to be the current node (the - bottommost node of the stack). */ - $stop = false; - $node = $this->stack[$n]; - $cat = $this->getElementCategory($node->tagName); - - /* 2. If node is an li, dd or dt element, then pop all - the nodes from the current node up to node, including - node, then stop this algorithm. */ - if($token['name'] === $node->tagName || ($token['name'] !== 'li' - && ($node->tagName === 'dd' || $node->tagName === 'dt'))) { - for($x = $stack_length; $x >= $n ; $x--) { - array_pop($this->stack); - } - - break; - } - - /* 3. If node is not in the formatting category, and is - not in the phrasing category, and is not an address or - div element, then stop this algorithm. */ - if($cat !== self::FORMATTING && $cat !== self::PHRASING && - $node->tagName !== 'address' && $node->tagName !== 'div') { - break; - } - } - - /* Finally, insert an HTML element with the same tag - name as the token's. */ - $this->insertElement($token); - break; - - /* A start tag token whose tag name is "plaintext" */ - case 'plaintext': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been - seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - return HTML5::PLAINTEXT; - break; - - /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", - "h5", "h6" */ - case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* If the stack of open elements has in scope an element whose - tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then - this is a parse error; pop elements from the stack until an - element with one of those tag names has been popped from the - stack. */ - while($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { - array_pop($this->stack); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - break; - - /* A start tag whose tag name is "a" */ - case 'a': - /* If the list of active formatting elements contains - an element whose tag name is "a" between the end of the - list and the last marker on the list (or the start of - the list if there is no marker on the list), then this - is a parse error; act as if an end tag with the tag name - "a" had been seen, then remove that element from the list - of active formatting elements and the stack of open - elements if the end tag didn't already remove it (it - might not have if the element is not in table scope). */ - $leng = count($this->a_formatting); - - for($n = $leng - 1; $n >= 0; $n--) { - if($this->a_formatting[$n] === self::MARKER) { - break; - - } elseif($this->a_formatting[$n]->nodeName === 'a') { - $this->emitToken(array( - 'name' => 'a', - 'type' => HTML5::ENDTAG - )); - break; - } - } - - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $el = $this->insertElement($token); - - /* Add that element to the list of active formatting - elements. */ - $this->a_formatting[] = $el; - break; - - /* A start tag whose tag name is one of: "b", "big", "em", "font", - "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ - case 'b': case 'big': case 'em': case 'font': case 'i': - case 'nobr': case 's': case 'small': case 'strike': - case 'strong': case 'tt': case 'u': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $el = $this->insertElement($token); - - /* Add that element to the list of active formatting - elements. */ - $this->a_formatting[] = $el; - break; - - /* A start tag token whose tag name is "button" */ - case 'button': - /* If the stack of open elements has a button element in scope, - then this is a parse error; act as if an end tag with the tag - name "button" had been seen, then reprocess the token. (We don't - do that. Unnecessary.) */ - if($this->elementInScope('button')) { - $this->inBody(array( - 'name' => 'button', - 'type' => HTML5::ENDTAG - )); - } - - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Insert a marker at the end of the list of active - formatting elements. */ - $this->a_formatting[] = self::MARKER; - break; - - /* A start tag token whose tag name is one of: "marquee", "object" */ - case 'marquee': case 'object': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Insert a marker at the end of the list of active - formatting elements. */ - $this->a_formatting[] = self::MARKER; - break; - - /* A start tag token whose tag name is "xmp" */ - case 'xmp': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Switch the content model flag to the CDATA state. */ - return HTML5::CDATA; - break; - - /* A start tag whose tag name is "table" */ - case 'table': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in table". */ - $this->mode = self::IN_TABLE; - break; - - /* A start tag whose tag name is one of: "area", "basefont", - "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ - case 'area': case 'basefont': case 'bgsound': case 'br': - case 'embed': case 'img': case 'param': case 'spacer': - case 'wbr': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Immediately pop the current node off the stack of open elements. */ - array_pop($this->stack); - break; - - /* A start tag whose tag name is "hr" */ - case 'hr': - /* If the stack of open elements has a p element in scope, - then act as if an end tag with the tag name p had been seen. */ - if($this->elementInScope('p')) { - $this->emitToken(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Immediately pop the current node off the stack of open elements. */ - array_pop($this->stack); - break; - - /* A start tag whose tag name is "image" */ - case 'image': - /* Parse error. Change the token's tag name to "img" and - reprocess it. (Don't ask.) */ - $token['name'] = 'img'; - return $this->inBody($token); - break; - - /* A start tag whose tag name is "input" */ - case 'input': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an input element for the token. */ - $element = $this->insertElement($token, false); - - /* If the form element pointer is not null, then associate the - input element with the form element pointed to by the form - element pointer. */ - $this->form_pointer !== null - ? $this->form_pointer->appendChild($element) - : end($this->stack)->appendChild($element); - - /* Pop that input element off the stack of open elements. */ - array_pop($this->stack); - break; - - /* A start tag whose tag name is "isindex" */ - case 'isindex': - /* Parse error. */ - // w/e - - /* If the form element pointer is not null, - then ignore the token. */ - if($this->form_pointer === null) { - /* Act as if a start tag token with the tag name "form" had - been seen. */ - $this->inBody(array( - 'name' => 'body', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a start tag token with the tag name "hr" had - been seen. */ - $this->inBody(array( - 'name' => 'hr', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a start tag token with the tag name "p" had - been seen. */ - $this->inBody(array( - 'name' => 'p', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a start tag token with the tag name "label" - had been seen. */ - $this->inBody(array( - 'name' => 'label', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - /* Act as if a stream of character tokens had been seen. */ - $this->insertText('This is a searchable index. '. - 'Insert your search keywords here: '); - - /* Act as if a start tag token with the tag name "input" - had been seen, with all the attributes from the "isindex" - token, except with the "name" attribute set to the value - "isindex" (ignoring any explicit "name" attribute). */ - $attr = $token['attr']; - $attr[] = array('name' => 'name', 'value' => 'isindex'); - - $this->inBody(array( - 'name' => 'input', - 'type' => HTML5::STARTTAG, - 'attr' => $attr - )); - - /* Act as if a stream of character tokens had been seen - (see below for what they should say). */ - $this->insertText('This is a searchable index. '. - 'Insert your search keywords here: '); - - /* Act as if an end tag token with the tag name "label" - had been seen. */ - $this->inBody(array( - 'name' => 'label', - 'type' => HTML5::ENDTAG - )); - - /* Act as if an end tag token with the tag name "p" had - been seen. */ - $this->inBody(array( - 'name' => 'p', - 'type' => HTML5::ENDTAG - )); - - /* Act as if a start tag token with the tag name "hr" had - been seen. */ - $this->inBody(array( - 'name' => 'hr', - 'type' => HTML5::ENDTAG - )); - - /* Act as if an end tag token with the tag name "form" had - been seen. */ - $this->inBody(array( - 'name' => 'form', - 'type' => HTML5::ENDTAG - )); - } - break; - - /* A start tag whose tag name is "textarea" */ - case 'textarea': - $this->insertElement($token); - - /* Switch the tokeniser's content model flag to the - RCDATA state. */ - return HTML5::RCDATA; - break; - - /* A start tag whose tag name is one of: "iframe", "noembed", - "noframes" */ - case 'iframe': case 'noembed': case 'noframes': - $this->insertElement($token); - - /* Switch the tokeniser's content model flag to the CDATA state. */ - return HTML5::CDATA; - break; - - /* A start tag whose tag name is "select" */ - case 'select': - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Change the insertion mode to "in select". */ - $this->mode = self::IN_SELECT; - break; - - /* A start or end tag whose tag name is one of: "caption", "col", - "colgroup", "frame", "frameset", "head", "option", "optgroup", - "tbody", "td", "tfoot", "th", "thead", "tr". */ - case 'caption': case 'col': case 'colgroup': case 'frame': - case 'frameset': case 'head': case 'option': case 'optgroup': - case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead': - case 'tr': - // Parse error. Ignore the token. - break; - - /* A start or end tag whose tag name is one of: "event-source", - "section", "nav", "article", "aside", "header", "footer", - "datagrid", "command" */ - case 'event-source': case 'section': case 'nav': case 'article': - case 'aside': case 'header': case 'footer': case 'datagrid': - case 'command': - // Work in progress! - break; - - /* A start tag token not covered by the previous entries */ - default: - /* Reconstruct the active formatting elements, if any. */ - $this->reconstructActiveFormattingElements(); - - $this->insertElement($token, true, true); - break; - } - break; - - case HTML5::ENDTAG: - switch($token['name']) { - /* An end tag with the tag name "body" */ - case 'body': - /* If the second element in the stack of open elements is - not a body element, this is a parse error. Ignore the token. - (innerHTML case) */ - if(count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { - // Ignore. - - /* If the current node is not the body element, then this - is a parse error. */ - } elseif(end($this->stack)->nodeName !== 'body') { - // Parse error. - } - - /* Change the insertion mode to "after body". */ - $this->mode = self::AFTER_BODY; - break; - - /* An end tag with the tag name "html" */ - case 'html': - /* Act as if an end tag with tag name "body" had been seen, - then, if that token wasn't ignored, reprocess the current - token. */ - $this->inBody(array( - 'name' => 'body', - 'type' => HTML5::ENDTAG - )); - - return $this->afterBody($token); - break; - - /* An end tag whose tag name is one of: "address", "blockquote", - "center", "dir", "div", "dl", "fieldset", "listing", "menu", - "ol", "pre", "ul" */ - case 'address': case 'blockquote': case 'center': case 'dir': - case 'div': case 'dl': case 'fieldset': case 'listing': - case 'menu': case 'ol': case 'pre': case 'ul': - /* If the stack of open elements has an element in scope - with the same tag name as that of the token, then generate - implied end tags. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(); - - /* Now, if the current node is not an element with - the same tag name as that of the token, then this - is a parse error. */ - // w/e - - /* If the stack of open elements has an element in - scope with the same tag name as that of the token, - then pop elements from this stack until an element - with that tag name has been popped from the stack. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === $token['name']) { - $n = -1; - } - - array_pop($this->stack); - } - } - break; - - /* An end tag whose tag name is "form" */ - case 'form': - /* If the stack of open elements has an element in scope - with the same tag name as that of the token, then generate - implied end tags. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(); - - } - - if(end($this->stack)->nodeName !== $token['name']) { - /* Now, if the current node is not an element with the - same tag name as that of the token, then this is a parse - error. */ - // w/e - - } else { - /* Otherwise, if the current node is an element with - the same tag name as that of the token pop that element - from the stack. */ - array_pop($this->stack); - } - - /* In any case, set the form element pointer to null. */ - $this->form_pointer = null; - break; - - /* An end tag whose tag name is "p" */ - case 'p': - /* If the stack of open elements has a p element in scope, - then generate implied end tags, except for p elements. */ - if($this->elementInScope('p')) { - $this->generateImpliedEndTags(array('p')); - - /* If the current node is not a p element, then this is - a parse error. */ - // k - - /* If the stack of open elements has a p element in - scope, then pop elements from this stack until the stack - no longer has a p element in scope. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->elementInScope('p')) { - array_pop($this->stack); - - } else { - break; - } - } - } - break; - - /* An end tag whose tag name is "dd", "dt", or "li" */ - case 'dd': case 'dt': case 'li': - /* If the stack of open elements has an element in scope - whose tag name matches the tag name of the token, then - generate implied end tags, except for elements with the - same tag name as the token. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(array($token['name'])); - - /* If the current node is not an element with the same - tag name as the token, then this is a parse error. */ - // w/e - - /* If the stack of open elements has an element in scope - whose tag name matches the tag name of the token, then - pop elements from this stack until an element with that - tag name has been popped from the stack. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === $token['name']) { - $n = -1; - } - - array_pop($this->stack); - } - } - break; - - /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", - "h5", "h6" */ - case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': - $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); - - /* If the stack of open elements has in scope an element whose - tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then - generate implied end tags. */ - if($this->elementInScope($elements)) { - $this->generateImpliedEndTags(); - - /* Now, if the current node is not an element with the same - tag name as that of the token, then this is a parse error. */ - // w/e - - /* If the stack of open elements has in scope an element - whose tag name is one of "h1", "h2", "h3", "h4", "h5", or - "h6", then pop elements from the stack until an element - with one of those tag names has been popped from the stack. */ - while($this->elementInScope($elements)) { - array_pop($this->stack); - } - } - break; - - /* An end tag whose tag name is one of: "a", "b", "big", "em", - "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ - case 'a': case 'b': case 'big': case 'em': case 'font': - case 'i': case 'nobr': case 's': case 'small': case 'strike': - case 'strong': case 'tt': case 'u': - /* 1. Let the formatting element be the last element in - the list of active formatting elements that: - * is between the end of the list and the last scope - marker in the list, if any, or the start of the list - otherwise, and - * has the same tag name as the token. - */ - while(true) { - for($a = count($this->a_formatting) - 1; $a >= 0; $a--) { - if($this->a_formatting[$a] === self::MARKER) { - break; - - } elseif($this->a_formatting[$a]->tagName === $token['name']) { - $formatting_element = $this->a_formatting[$a]; - $in_stack = in_array($formatting_element, $this->stack, true); - $fe_af_pos = $a; - break; - } - } - - /* If there is no such node, or, if that node is - also in the stack of open elements but the element - is not in scope, then this is a parse error. Abort - these steps. The token is ignored. */ - if(!isset($formatting_element) || ($in_stack && - !$this->elementInScope($token['name']))) { - break; - - /* Otherwise, if there is such a node, but that node - is not in the stack of open elements, then this is a - parse error; remove the element from the list, and - abort these steps. */ - } elseif(isset($formatting_element) && !$in_stack) { - unset($this->a_formatting[$fe_af_pos]); - $this->a_formatting = array_merge($this->a_formatting); - break; - } - - /* 2. Let the furthest block be the topmost node in the - stack of open elements that is lower in the stack - than the formatting element, and is not an element in - the phrasing or formatting categories. There might - not be one. */ - $fe_s_pos = array_search($formatting_element, $this->stack, true); - $length = count($this->stack); - - for($s = $fe_s_pos + 1; $s < $length; $s++) { - $category = $this->getElementCategory($this->stack[$s]->nodeName); - - if($category !== self::PHRASING && $category !== self::FORMATTING) { - $furthest_block = $this->stack[$s]; - } - } - - /* 3. If there is no furthest block, then the UA must - skip the subsequent steps and instead just pop all - the nodes from the bottom of the stack of open - elements, from the current node up to the formatting - element, and remove the formatting element from the - list of active formatting elements. */ - if(!isset($furthest_block)) { - for($n = $length - 1; $n >= $fe_s_pos; $n--) { - array_pop($this->stack); - } - - unset($this->a_formatting[$fe_af_pos]); - $this->a_formatting = array_merge($this->a_formatting); - break; - } - - /* 4. Let the common ancestor be the element - immediately above the formatting element in the stack - of open elements. */ - $common_ancestor = $this->stack[$fe_s_pos - 1]; - - /* 5. If the furthest block has a parent node, then - remove the furthest block from its parent node. */ - if($furthest_block->parentNode !== null) { - $furthest_block->parentNode->removeChild($furthest_block); - } - - /* 6. Let a bookmark note the position of the - formatting element in the list of active formatting - elements relative to the elements on either side - of it in the list. */ - $bookmark = $fe_af_pos; - - /* 7. Let node and last node be the furthest block. - Follow these steps: */ - $node = $furthest_block; - $last_node = $furthest_block; - - while(true) { - for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { - /* 7.1 Let node be the element immediately - prior to node in the stack of open elements. */ - $node = $this->stack[$n]; - - /* 7.2 If node is not in the list of active - formatting elements, then remove node from - the stack of open elements and then go back - to step 1. */ - if(!in_array($node, $this->a_formatting, true)) { - unset($this->stack[$n]); - $this->stack = array_merge($this->stack); - - } else { - break; - } - } - - /* 7.3 Otherwise, if node is the formatting - element, then go to the next step in the overall - algorithm. */ - if($node === $formatting_element) { - break; - - /* 7.4 Otherwise, if last node is the furthest - block, then move the aforementioned bookmark to - be immediately after the node in the list of - active formatting elements. */ - } elseif($last_node === $furthest_block) { - $bookmark = array_search($node, $this->a_formatting, true) + 1; - } - - /* 7.5 If node has any children, perform a - shallow clone of node, replace the entry for - node in the list of active formatting elements - with an entry for the clone, replace the entry - for node in the stack of open elements with an - entry for the clone, and let node be the clone. */ - if($node->hasChildNodes()) { - $clone = $node->cloneNode(); - $s_pos = array_search($node, $this->stack, true); - $a_pos = array_search($node, $this->a_formatting, true); - - $this->stack[$s_pos] = $clone; - $this->a_formatting[$a_pos] = $clone; - $node = $clone; - } - - /* 7.6 Insert last node into node, first removing - it from its previous parent node if any. */ - if($last_node->parentNode !== null) { - $last_node->parentNode->removeChild($last_node); - } - - $node->appendChild($last_node); - - /* 7.7 Let last node be node. */ - $last_node = $node; - } - - /* 8. Insert whatever last node ended up being in - the previous step into the common ancestor node, - first removing it from its previous parent node if - any. */ - if($last_node->parentNode !== null) { - $last_node->parentNode->removeChild($last_node); - } - - $common_ancestor->appendChild($last_node); - - /* 9. Perform a shallow clone of the formatting - element. */ - $clone = $formatting_element->cloneNode(); - - /* 10. Take all of the child nodes of the furthest - block and append them to the clone created in the - last step. */ - while($furthest_block->hasChildNodes()) { - $child = $furthest_block->firstChild; - $furthest_block->removeChild($child); - $clone->appendChild($child); - } - - /* 11. Append that clone to the furthest block. */ - $furthest_block->appendChild($clone); - - /* 12. Remove the formatting element from the list - of active formatting elements, and insert the clone - into the list of active formatting elements at the - position of the aforementioned bookmark. */ - $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); - unset($this->a_formatting[$fe_af_pos]); - $this->a_formatting = array_merge($this->a_formatting); - - $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); - $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); - $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); - - /* 13. Remove the formatting element from the stack - of open elements, and insert the clone into the stack - of open elements immediately after (i.e. in a more - deeply nested position than) the position of the - furthest block in that stack. */ - $fe_s_pos = array_search($formatting_element, $this->stack, true); - $fb_s_pos = array_search($furthest_block, $this->stack, true); - unset($this->stack[$fe_s_pos]); - - $s_part1 = array_slice($this->stack, 0, $fb_s_pos); - $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); - $this->stack = array_merge($s_part1, array($clone), $s_part2); - - /* 14. Jump back to step 1 in this series of steps. */ - unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); - } - break; - - /* An end tag token whose tag name is one of: "button", - "marquee", "object" */ - case 'button': case 'marquee': case 'object': - /* If the stack of open elements has an element in scope whose - tag name matches the tag name of the token, then generate implied - tags. */ - if($this->elementInScope($token['name'])) { - $this->generateImpliedEndTags(); - - /* Now, if the current node is not an element with the same - tag name as the token, then this is a parse error. */ - // k - - /* Now, if the stack of open elements has an element in scope - whose tag name matches the tag name of the token, then pop - elements from the stack until that element has been popped from - the stack, and clear the list of active formatting elements up - to the last marker. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === $token['name']) { - $n = -1; - } - - array_pop($this->stack); - } - - $marker = end(array_keys($this->a_formatting, self::MARKER, true)); - - for($n = count($this->a_formatting) - 1; $n > $marker; $n--) { - array_pop($this->a_formatting); - } - } - break; - - /* Or an end tag whose tag name is one of: "area", "basefont", - "bgsound", "br", "embed", "hr", "iframe", "image", "img", - "input", "isindex", "noembed", "noframes", "param", "select", - "spacer", "table", "textarea", "wbr" */ - case 'area': case 'basefont': case 'bgsound': case 'br': - case 'embed': case 'hr': case 'iframe': case 'image': - case 'img': case 'input': case 'isindex': case 'noembed': - case 'noframes': case 'param': case 'select': case 'spacer': - case 'table': case 'textarea': case 'wbr': - // Parse error. Ignore the token. - break; - - /* An end tag token not covered by the previous entries */ - default: - for($n = count($this->stack) - 1; $n >= 0; $n--) { - /* Initialise node to be the current node (the bottommost - node of the stack). */ - $node = end($this->stack); - - /* If node has the same tag name as the end tag token, - then: */ - if($token['name'] === $node->nodeName) { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* If the tag name of the end tag token does not - match the tag name of the current node, this is a - parse error. */ - // k - - /* Pop all the nodes from the current node up to - node, including node, then stop this algorithm. */ - for($x = count($this->stack) - $n; $x >= $n; $x--) { - array_pop($this->stack); - } - - } else { - $category = $this->getElementCategory($node); - - if($category !== self::SPECIAL && $category !== self::SCOPING) { - /* Otherwise, if node is in neither the formatting - category nor the phrasing category, then this is a - parse error. Stop this algorithm. The end tag token - is ignored. */ - return false; - } - } - } - break; - } - break; - } - } - - private function inTable($token) { - $clear = array('html', 'table'); - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $text = $this->dom->createTextNode($token['data']); - end($this->stack)->appendChild($text); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - end($this->stack)->appendChild($comment); - - /* A start tag whose tag name is "caption" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'caption') { - /* Clear the stack back to a table context. */ - $this->clearStackToTableContext($clear); - - /* Insert a marker at the end of the list of active - formatting elements. */ - $this->a_formatting[] = self::MARKER; - - /* Insert an HTML element for the token, then switch the - insertion mode to "in caption". */ - $this->insertElement($token); - $this->mode = self::IN_CAPTION; - - /* A start tag whose tag name is "colgroup" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'colgroup') { - /* Clear the stack back to a table context. */ - $this->clearStackToTableContext($clear); - - /* Insert an HTML element for the token, then switch the - insertion mode to "in column group". */ - $this->insertElement($token); - $this->mode = self::IN_CGROUP; - - /* A start tag whose tag name is "col" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'col') { - $this->inTable(array( - 'name' => 'colgroup', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - $this->inColumnGroup($token); - - /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('tbody', 'tfoot', 'thead'))) { - /* Clear the stack back to a table context. */ - $this->clearStackToTableContext($clear); - - /* Insert an HTML element for the token, then switch the insertion - mode to "in table body". */ - $this->insertElement($token); - $this->mode = self::IN_TBODY; - - /* A start tag whose tag name is one of: "td", "th", "tr" */ - } elseif($token['type'] === HTML5::STARTTAG && - in_array($token['name'], array('td', 'th', 'tr'))) { - /* Act as if a start tag token with the tag name "tbody" had been - seen, then reprocess the current token. */ - $this->inTable(array( - 'name' => 'tbody', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inTableBody($token); - - /* A start tag whose tag name is "table" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'table') { - /* Parse error. Act as if an end tag token with the tag name "table" - had been seen, then, if that token wasn't ignored, reprocess the - current token. */ - $this->inTable(array( - 'name' => 'table', - 'type' => HTML5::ENDTAG - )); - - return $this->mainPhase($token); - - /* An end tag whose tag name is "table" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'table') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - return false; - - /* Otherwise: */ - } else { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* Now, if the current node is not a table element, then this - is a parse error. */ - // w/e - - /* Pop elements from this stack until a table element has been - popped from the stack. */ - while(true) { - $current = end($this->stack)->nodeName; - array_pop($this->stack); - - if($current === 'table') { - break; - } - } - - /* Reset the insertion mode appropriately. */ - $this->resetInsertionMode(); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td', - 'tfoot', 'th', 'thead', 'tr'))) { - // Parse error. Ignore the token. - - /* Anything else */ - } else { - /* Parse error. Process the token as if the insertion mode was "in - body", with the following exception: */ - - /* If the current node is a table, tbody, tfoot, thead, or tr - element, then, whenever a node would be inserted into the current - node, it must instead be inserted into the foster parent element. */ - if(in_array(end($this->stack)->nodeName, - array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { - /* The foster parent element is the parent element of the last - table element in the stack of open elements, if there is a - table element and it has such a parent element. If there is no - table element in the stack of open elements (innerHTML case), - then the foster parent element is the first element in the - stack of open elements (the html element). Otherwise, if there - is a table element in the stack of open elements, but the last - table element in the stack of open elements has no parent, or - its parent node is not an element, then the foster parent - element is the element before the last table element in the - stack of open elements. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === 'table') { - $table = $this->stack[$n]; - break; - } - } - - if(isset($table) && $table->parentNode !== null) { - $this->foster_parent = $table->parentNode; - - } elseif(!isset($table)) { - $this->foster_parent = $this->stack[0]; - - } elseif(isset($table) && ($table->parentNode === null || - $table->parentNode->nodeType !== XML_ELEMENT_NODE)) { - $this->foster_parent = $this->stack[$n - 1]; - } - } - - $this->inBody($token); - } - } - - private function inCaption($token) { - /* An end tag whose tag name is "caption" */ - if($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore - - /* Otherwise: */ - } else { - /* Generate implied end tags. */ - $this->generateImpliedEndTags(); - - /* Now, if the current node is not a caption element, then this - is a parse error. */ - // w/e - - /* Pop elements from this stack until a caption element has - been popped from the stack. */ - while(true) { - $node = end($this->stack)->nodeName; - array_pop($this->stack); - - if($node === 'caption') { - break; - } - } - - /* Clear the list of active formatting elements up to the last - marker. */ - $this->clearTheActiveFormattingElementsUpToTheLastMarker(); - - /* Switch the insertion mode to "in table". */ - $this->mode = self::IN_TABLE; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag - name is "table" */ - } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', - 'thead', 'tr'))) || ($token['type'] === HTML5::ENDTAG && - $token['name'] === 'table')) { - /* Parse error. Act as if an end tag with the tag name "caption" - had been seen, then, if that token wasn't ignored, reprocess the - current token. */ - $this->inCaption(array( - 'name' => 'caption', - 'type' => HTML5::ENDTAG - )); - - return $this->inTable($token); - - /* An end tag whose tag name is one of: "body", "col", "colgroup", - "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th', - 'thead', 'tr'))) { - // Parse error. Ignore the token. - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in body". */ - $this->inBody($token); - } - } - - private function inColumnGroup($token) { - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $text = $this->dom->createTextNode($token['data']); - end($this->stack)->appendChild($text); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - end($this->stack)->appendChild($comment); - - /* A start tag whose tag name is "col" */ - } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { - /* Insert a col element for the token. Immediately pop the current - node off the stack of open elements. */ - $this->insertElement($token); - array_pop($this->stack); - - /* An end tag whose tag name is "colgroup" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'colgroup') { - /* If the current node is the root html element, then this is a - parse error, ignore the token. (innerHTML case) */ - if(end($this->stack)->nodeName === 'html') { - // Ignore - - /* Otherwise, pop the current node (which will be a colgroup - element) from the stack of open elements. Switch the insertion - mode to "in table". */ - } else { - array_pop($this->stack); - $this->mode = self::IN_TABLE; - } - - /* An end tag whose tag name is "col" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { - /* Parse error. Ignore the token. */ - - /* Anything else */ - } else { - /* Act as if an end tag with the tag name "colgroup" had been seen, - and then, if that token wasn't ignored, reprocess the current token. */ - $this->inColumnGroup(array( - 'name' => 'colgroup', - 'type' => HTML5::ENDTAG - )); - - return $this->inTable($token); - } - } - - private function inTableBody($token) { - $clear = array('tbody', 'tfoot', 'thead', 'html'); - - /* A start tag whose tag name is "tr" */ - if($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { - /* Clear the stack back to a table body context. */ - $this->clearStackToTableContext($clear); - - /* Insert a tr element for the token, then switch the insertion - mode to "in row". */ - $this->insertElement($token); - $this->mode = self::IN_ROW; - - /* A start tag whose tag name is one of: "th", "td" */ - } elseif($token['type'] === HTML5::STARTTAG && - ($token['name'] === 'th' || $token['name'] === 'td')) { - /* Parse error. Act as if a start tag with the tag name "tr" had - been seen, then reprocess the current token. */ - $this->inTableBody(array( - 'name' => 'tr', - 'type' => HTML5::STARTTAG, - 'attr' => array() - )); - - return $this->inRow($token); - - /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ - } elseif($token['type'] === HTML5::ENDTAG && - in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore - - /* Otherwise: */ - } else { - /* Clear the stack back to a table body context. */ - $this->clearStackToTableContext($clear); - - /* Pop the current node from the stack of open elements. Switch - the insertion mode to "in table". */ - array_pop($this->stack); - $this->mode = self::IN_TABLE; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ - } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead'))) || - ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')) { - /* If the stack of open elements does not have a tbody, thead, or - tfoot element in table scope, this is a parse error. Ignore the - token. (innerHTML case) */ - if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Clear the stack back to a table body context. */ - $this->clearStackToTableContext($clear); - - /* Act as if an end tag with the same tag name as the current - node ("tbody", "tfoot", or "thead") had been seen, then - reprocess the current token. */ - $this->inTableBody(array( - 'name' => end($this->stack)->nodeName, - 'type' => HTML5::ENDTAG - )); - - return $this->mainPhase($token); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html", "td", "th", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { - /* Parse error. Ignore the token. */ - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in table". */ - $this->inTable($token); - } - } - - private function inRow($token) { - $clear = array('tr', 'html'); - - /* A start tag whose tag name is one of: "th", "td" */ - if($token['type'] === HTML5::STARTTAG && - ($token['name'] === 'th' || $token['name'] === 'td')) { - /* Clear the stack back to a table row context. */ - $this->clearStackToTableContext($clear); - - /* Insert an HTML element for the token, then switch the insertion - mode to "in cell". */ - $this->insertElement($token); - $this->mode = self::IN_CELL; - - /* Insert a marker at the end of the list of active formatting - elements. */ - $this->a_formatting[] = self::MARKER; - - /* An end tag whose tag name is "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Clear the stack back to a table row context. */ - $this->clearStackToTableContext($clear); - - /* Pop the current node (which will be a tr element) from the - stack of open elements. Switch the insertion mode to "in table - body". */ - array_pop($this->stack); - $this->mode = self::IN_TBODY; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) { - /* Act as if an end tag with the tag name "tr" had been seen, then, - if that token wasn't ignored, reprocess the current token. */ - $this->inRow(array( - 'name' => 'tr', - 'type' => HTML5::ENDTAG - )); - - return $this->inCell($token); - - /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ - } elseif($token['type'] === HTML5::ENDTAG && - in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Otherwise, act as if an end tag with the tag name "tr" had - been seen, then reprocess the current token. */ - $this->inRow(array( - 'name' => 'tr', - 'type' => HTML5::ENDTAG - )); - - return $this->inCell($token); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html", "td", "th" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { - /* Parse error. Ignore the token. */ - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in table". */ - $this->inTable($token); - } - } - - private function inCell($token) { - /* An end tag whose tag name is one of: "td", "th" */ - if($token['type'] === HTML5::ENDTAG && - ($token['name'] === 'td' || $token['name'] === 'th')) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as that of the token, then this is a - parse error and the token must be ignored. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise: */ - } else { - /* Generate implied end tags, except for elements with the same - tag name as the token. */ - $this->generateImpliedEndTags(array($token['name'])); - - /* Now, if the current node is not an element with the same tag - name as the token, then this is a parse error. */ - // k - - /* Pop elements from this stack until an element with the same - tag name as the token has been popped from the stack. */ - while(true) { - $node = end($this->stack)->nodeName; - array_pop($this->stack); - - if($node === $token['name']) { - break; - } - } - - /* Clear the list of active formatting elements up to the last - marker. */ - $this->clearTheActiveFormattingElementsUpToTheLastMarker(); - - /* Switch the insertion mode to "in row". (The current node - will be a tr element at this point.) */ - $this->mode = self::IN_ROW; - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', - 'thead', 'tr'))) { - /* If the stack of open elements does not have a td or th element - in table scope, then this is a parse error; ignore the token. - (innerHTML case) */ - if(!$this->elementInScope(array('td', 'th'), true)) { - // Ignore. - - /* Otherwise, close the cell (see below) and reprocess the current - token. */ - } else { - $this->closeCell(); - return $this->inRow($token); - } - - /* A start tag whose tag name is one of: "caption", "col", "colgroup", - "tbody", "td", "tfoot", "th", "thead", "tr" */ - } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], - array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', - 'thead', 'tr'))) { - /* If the stack of open elements does not have a td or th element - in table scope, then this is a parse error; ignore the token. - (innerHTML case) */ - if(!$this->elementInScope(array('td', 'th'), true)) { - // Ignore. - - /* Otherwise, close the cell (see below) and reprocess the current - token. */ - } else { - $this->closeCell(); - return $this->inRow($token); - } - - /* An end tag whose tag name is one of: "body", "caption", "col", - "colgroup", "html" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('body', 'caption', 'col', 'colgroup', 'html'))) { - /* Parse error. Ignore the token. */ - - /* An end tag whose tag name is one of: "table", "tbody", "tfoot", - "thead", "tr" */ - } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], - array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { - /* If the stack of open elements does not have an element in table - scope with the same tag name as that of the token (which can only - happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), - then this is a parse error and the token must be ignored. */ - if(!$this->elementInScope($token['name'], true)) { - // Ignore. - - /* Otherwise, close the cell (see below) and reprocess the current - token. */ - } else { - $this->closeCell(); - return $this->inRow($token); - } - - /* Anything else */ - } else { - /* Process the token as if the insertion mode was "in body". */ - $this->inBody($token); - } - } - - private function inSelect($token) { - /* Handle the token as follows: */ - - /* A character token */ - if($token['type'] === HTML5::CHARACTR) { - /* Append the token's character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag token whose tag name is "option" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'option') { - /* If the current node is an option element, act as if an end tag - with the tag name "option" had been seen. */ - if(end($this->stack)->nodeName === 'option') { - $this->inSelect(array( - 'name' => 'option', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* A start tag token whose tag name is "optgroup" */ - } elseif($token['type'] === HTML5::STARTTAG && - $token['name'] === 'optgroup') { - /* If the current node is an option element, act as if an end tag - with the tag name "option" had been seen. */ - if(end($this->stack)->nodeName === 'option') { - $this->inSelect(array( - 'name' => 'option', - 'type' => HTML5::ENDTAG - )); - } - - /* If the current node is an optgroup element, act as if an end tag - with the tag name "optgroup" had been seen. */ - if(end($this->stack)->nodeName === 'optgroup') { - $this->inSelect(array( - 'name' => 'optgroup', - 'type' => HTML5::ENDTAG - )); - } - - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* An end tag token whose tag name is "optgroup" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'optgroup') { - /* First, if the current node is an option element, and the node - immediately before it in the stack of open elements is an optgroup - element, then act as if an end tag with the tag name "option" had - been seen. */ - $elements_in_stack = count($this->stack); - - if($this->stack[$elements_in_stack - 1]->nodeName === 'option' && - $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup') { - $this->inSelect(array( - 'name' => 'option', - 'type' => HTML5::ENDTAG - )); - } - - /* If the current node is an optgroup element, then pop that node - from the stack of open elements. Otherwise, this is a parse error, - ignore the token. */ - if($this->stack[$elements_in_stack - 1] === 'optgroup') { - array_pop($this->stack); - } - - /* An end tag token whose tag name is "option" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'option') { - /* If the current node is an option element, then pop that node - from the stack of open elements. Otherwise, this is a parse error, - ignore the token. */ - if(end($this->stack)->nodeName === 'option') { - array_pop($this->stack); - } - - /* An end tag whose tag name is "select" */ - } elseif($token['type'] === HTML5::ENDTAG && - $token['name'] === 'select') { - /* If the stack of open elements does not have an element in table - scope with the same tag name as the token, this is a parse error. - Ignore the token. (innerHTML case) */ - if(!$this->elementInScope($token['name'], true)) { - // w/e - - /* Otherwise: */ - } else { - /* Pop elements from the stack of open elements until a select - element has been popped from the stack. */ - while(true) { - $current = end($this->stack)->nodeName; - array_pop($this->stack); - - if($current === 'select') { - break; - } - } - - /* Reset the insertion mode appropriately. */ - $this->resetInsertionMode(); - } - - /* A start tag whose tag name is "select" */ - } elseif($token['name'] === 'select' && - $token['type'] === HTML5::STARTTAG) { - /* Parse error. Act as if the token had been an end tag with the - tag name "select" instead. */ - $this->inSelect(array( - 'name' => 'select', - 'type' => HTML5::ENDTAG - )); - - /* An end tag whose tag name is one of: "caption", "table", "tbody", - "tfoot", "thead", "tr", "td", "th" */ - } elseif(in_array($token['name'], array('caption', 'table', 'tbody', - 'tfoot', 'thead', 'tr', 'td', 'th')) && $token['type'] === HTML5::ENDTAG) { - /* Parse error. */ - // w/e - - /* If the stack of open elements has an element in table scope with - the same tag name as that of the token, then act as if an end tag - with the tag name "select" had been seen, and reprocess the token. - Otherwise, ignore the token. */ - if($this->elementInScope($token['name'], true)) { - $this->inSelect(array( - 'name' => 'select', - 'type' => HTML5::ENDTAG - )); - - $this->mainPhase($token); - } - - /* Anything else */ - } else { - /* Parse error. Ignore the token. */ - } - } - - private function afterBody($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Process the token as it would be processed if the insertion mode - was "in body". */ - $this->inBody($token); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the first element in the stack of open - elements (the html element), with the data attribute set to the - data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - $this->stack[0]->appendChild($comment); - - /* An end tag with the tag name "html" */ - } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { - /* If the parser was originally created in order to handle the - setting of an element's innerHTML attribute, this is a parse error; - ignore the token. (The element will be an html element in this - case.) (innerHTML case) */ - - /* Otherwise, switch to the trailing end phase. */ - $this->phase = self::END_PHASE; - - /* Anything else */ - } else { - /* Parse error. Set the insertion mode to "in body" and reprocess - the token. */ - $this->mode = self::IN_BODY; - return $this->inBody($token); - } - } - - private function inFrameset($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* A start tag with the tag name "frameset" */ - } elseif($token['name'] === 'frameset' && - $token['type'] === HTML5::STARTTAG) { - $this->insertElement($token); - - /* An end tag with the tag name "frameset" */ - } elseif($token['name'] === 'frameset' && - $token['type'] === HTML5::ENDTAG) { - /* If the current node is the root html element, then this is a - parse error; ignore the token. (innerHTML case) */ - if(end($this->stack)->nodeName === 'html') { - // Ignore - - } else { - /* Otherwise, pop the current node from the stack of open - elements. */ - array_pop($this->stack); - - /* If the parser was not originally created in order to handle - the setting of an element's innerHTML attribute (innerHTML case), - and the current node is no longer a frameset element, then change - the insertion mode to "after frameset". */ - $this->mode = self::AFTR_FRAME; - } - - /* A start tag with the tag name "frame" */ - } elseif($token['name'] === 'frame' && - $token['type'] === HTML5::STARTTAG) { - /* Insert an HTML element for the token. */ - $this->insertElement($token); - - /* Immediately pop the current node off the stack of open elements. */ - array_pop($this->stack); - - /* A start tag with the tag name "noframes" */ - } elseif($token['name'] === 'noframes' && - $token['type'] === HTML5::STARTTAG) { - /* Process the token as if the insertion mode had been "in body". */ - $this->inBody($token); - - /* Anything else */ - } else { - /* Parse error. Ignore the token. */ - } - } - - private function afterFrameset($token) { - /* Handle the token as follows: */ - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ - if($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Append the character to the current node. */ - $this->insertText($token['data']); - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the current node with the data - attribute set to the data given in the comment token. */ - $this->insertComment($token['data']); - - /* An end tag with the tag name "html" */ - } elseif($token['name'] === 'html' && - $token['type'] === HTML5::ENDTAG) { - /* Switch to the trailing end phase. */ - $this->phase = self::END_PHASE; - - /* A start tag with the tag name "noframes" */ - } elseif($token['name'] === 'noframes' && - $token['type'] === HTML5::STARTTAG) { - /* Process the token as if the insertion mode had been "in body". */ - $this->inBody($token); - - /* Anything else */ - } else { - /* Parse error. Ignore the token. */ - } - } - - private function trailingEndPhase($token) { - /* After the main phase, as each token is emitted from the tokenisation - stage, it must be processed as described in this section. */ - - /* A DOCTYPE token */ - if($token['type'] === HTML5::DOCTYPE) { - // Parse error. Ignore the token. - - /* A comment token */ - } elseif($token['type'] === HTML5::COMMENT) { - /* Append a Comment node to the Document object with the data - attribute set to the data given in the comment token. */ - $comment = $this->dom->createComment($token['data']); - $this->dom->appendChild($comment); - - /* A character token that is one of one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE */ - } elseif($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { - /* Process the token as it would be processed in the main phase. */ - $this->mainPhase($token); - - /* A character token that is not one of U+0009 CHARACTER TABULATION, - U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), - or U+0020 SPACE. Or a start tag token. Or an end tag token. */ - } elseif(($token['type'] === HTML5::CHARACTR && - preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || - $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG) { - /* Parse error. Switch back to the main phase and reprocess the - token. */ - $this->phase = self::MAIN_PHASE; - return $this->mainPhase($token); - - /* An end-of-file token */ - } elseif($token['type'] === HTML5::EOF) { - /* OMG DONE!! */ - } - } - - private function insertElement($token, $append = true, $check = false) { - // Proprietary workaround for libxml2's limitations with tag names - if ($check) { - // Slightly modified HTML5 tag-name modification, - // removing anything that's not an ASCII letter, digit, or hyphen - $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); - // Remove leading hyphens and numbers - $token['name'] = ltrim($token['name'], '-0..9'); - // In theory, this should ever be needed, but just in case - if ($token['name'] === '') $token['name'] = 'span'; // arbitrary generic choice - } - - $el = $this->dom->createElement($token['name']); - - foreach($token['attr'] as $attr) { - if(!$el->hasAttribute($attr['name'])) { - $el->setAttribute($attr['name'], $attr['value']); - } - } - - $this->appendToRealParent($el); - $this->stack[] = $el; - - return $el; - } - - private function insertText($data) { - $text = $this->dom->createTextNode($data); - $this->appendToRealParent($text); - } - - private function insertComment($data) { - $comment = $this->dom->createComment($data); - $this->appendToRealParent($comment); - } - - private function appendToRealParent($node) { - if($this->foster_parent === null) { - end($this->stack)->appendChild($node); - - } elseif($this->foster_parent !== null) { - /* If the foster parent element is the parent element of the - last table element in the stack of open elements, then the new - node must be inserted immediately before the last table element - in the stack of open elements in the foster parent element; - otherwise, the new node must be appended to the foster parent - element. */ - for($n = count($this->stack) - 1; $n >= 0; $n--) { - if($this->stack[$n]->nodeName === 'table' && - $this->stack[$n]->parentNode !== null) { - $table = $this->stack[$n]; - break; - } - } - - if(isset($table) && $this->foster_parent->isSameNode($table->parentNode)) - $this->foster_parent->insertBefore($node, $table); - else - $this->foster_parent->appendChild($node); - - $this->foster_parent = null; - } - } - - private function elementInScope($el, $table = false) { - if(is_array($el)) { - foreach($el as $element) { - if($this->elementInScope($element, $table)) { - return true; - } - } - - return false; - } - - $leng = count($this->stack); - - for($n = 0; $n < $leng; $n++) { - /* 1. Initialise node to be the current node (the bottommost node of - the stack). */ - $node = $this->stack[$leng - 1 - $n]; - - if($node->tagName === $el) { - /* 2. If node is the target node, terminate in a match state. */ - return true; - - } elseif($node->tagName === 'table') { - /* 3. Otherwise, if node is a table element, terminate in a failure - state. */ - return false; - - } elseif($table === true && in_array($node->tagName, array('caption', 'td', - 'th', 'button', 'marquee', 'object'))) { - /* 4. Otherwise, if the algorithm is the "has an element in scope" - variant (rather than the "has an element in table scope" variant), - and node is one of the following, terminate in a failure state. */ - return false; - - } elseif($node === $node->ownerDocument->documentElement) { - /* 5. Otherwise, if node is an html element (root element), terminate - in a failure state. (This can only happen if the node is the topmost - node of the stack of open elements, and prevents the next step from - being invoked if there are no more elements in the stack.) */ - return false; - } - - /* Otherwise, set node to the previous entry in the stack of open - elements and return to step 2. (This will never fail, since the loop - will always terminate in the previous step if the top of the stack - is reached.) */ - } - } - - private function reconstructActiveFormattingElements() { - /* 1. If there are no entries in the list of active formatting elements, - then there is nothing to reconstruct; stop this algorithm. */ - $formatting_elements = count($this->a_formatting); - - if($formatting_elements === 0) { - return false; - } - - /* 3. Let entry be the last (most recently added) element in the list - of active formatting elements. */ - $entry = end($this->a_formatting); - - /* 2. If the last (most recently added) entry in the list of active - formatting elements is a marker, or if it is an element that is in the - stack of open elements, then there is nothing to reconstruct; stop this - algorithm. */ - if($entry === self::MARKER || in_array($entry, $this->stack, true)) { - return false; - } - - for($a = $formatting_elements - 1; $a >= 0; true) { - /* 4. If there are no entries before entry in the list of active - formatting elements, then jump to step 8. */ - if($a === 0) { - $step_seven = false; - break; - } - - /* 5. Let entry be the entry one earlier than entry in the list of - active formatting elements. */ - $a--; - $entry = $this->a_formatting[$a]; - - /* 6. If entry is neither a marker nor an element that is also in - thetack of open elements, go to step 4. */ - if($entry === self::MARKER || in_array($entry, $this->stack, true)) { - break; - } - } - - while(true) { - /* 7. Let entry be the element one later than entry in the list of - active formatting elements. */ - if(isset($step_seven) && $step_seven === true) { - $a++; - $entry = $this->a_formatting[$a]; - } - - /* 8. Perform a shallow clone of the element entry to obtain clone. */ - $clone = $entry->cloneNode(); - - /* 9. Append clone to the current node and push it onto the stack - of open elements so that it is the new current node. */ - end($this->stack)->appendChild($clone); - $this->stack[] = $clone; - - /* 10. Replace the entry for entry in the list with an entry for - clone. */ - $this->a_formatting[$a] = $clone; - - /* 11. If the entry for clone in the list of active formatting - elements is not the last entry in the list, return to step 7. */ - if(end($this->a_formatting) !== $clone) { - $step_seven = true; - } else { - break; - } - } - } - - private function clearTheActiveFormattingElementsUpToTheLastMarker() { - /* When the steps below require the UA to clear the list of active - formatting elements up to the last marker, the UA must perform the - following steps: */ - - while(true) { - /* 1. Let entry be the last (most recently added) entry in the list - of active formatting elements. */ - $entry = end($this->a_formatting); - - /* 2. Remove entry from the list of active formatting elements. */ - array_pop($this->a_formatting); - - /* 3. If entry was a marker, then stop the algorithm at this point. - The list has been cleared up to the last marker. */ - if($entry === self::MARKER) { - break; - } - } - } - - private function generateImpliedEndTags($exclude = array()) { - /* When the steps below require the UA to generate implied end tags, - then, if the current node is a dd element, a dt element, an li element, - a p element, a td element, a th element, or a tr element, the UA must - act as if an end tag with the respective tag name had been seen and - then generate implied end tags again. */ - $node = end($this->stack); - $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); - - while(in_array(end($this->stack)->nodeName, $elements)) { - array_pop($this->stack); - } - } - - private function getElementCategory($node) { - $name = $node->tagName; - if(in_array($name, $this->special)) - return self::SPECIAL; - - elseif(in_array($name, $this->scoping)) - return self::SCOPING; - - elseif(in_array($name, $this->formatting)) - return self::FORMATTING; - - else - return self::PHRASING; - } - - private function clearStackToTableContext($elements) { - /* When the steps above require the UA to clear the stack back to a - table context, it means that the UA must, while the current node is not - a table element or an html element, pop elements from the stack of open - elements. If this causes any elements to be popped from the stack, then - this is a parse error. */ - while(true) { - $node = end($this->stack)->nodeName; - - if(in_array($node, $elements)) { - break; - } else { - array_pop($this->stack); - } - } - } - - private function resetInsertionMode() { - /* 1. Let last be false. */ - $last = false; - $leng = count($this->stack); - - for($n = $leng - 1; $n >= 0; $n--) { - /* 2. Let node be the last node in the stack of open elements. */ - $node = $this->stack[$n]; - - /* 3. If node is the first node in the stack of open elements, then - set last to true. If the element whose innerHTML attribute is being - set is neither a td element nor a th element, then set node to the - element whose innerHTML attribute is being set. (innerHTML case) */ - if($this->stack[0]->isSameNode($node)) { - $last = true; - } - - /* 4. If node is a select element, then switch the insertion mode to - "in select" and abort these steps. (innerHTML case) */ - if($node->nodeName === 'select') { - $this->mode = self::IN_SELECT; - break; - - /* 5. If node is a td or th element, then switch the insertion mode - to "in cell" and abort these steps. */ - } elseif($node->nodeName === 'td' || $node->nodeName === 'th') { - $this->mode = self::IN_CELL; - break; - - /* 6. If node is a tr element, then switch the insertion mode to - "in row" and abort these steps. */ - } elseif($node->nodeName === 'tr') { - $this->mode = self::IN_ROW; - break; - - /* 7. If node is a tbody, thead, or tfoot element, then switch the - insertion mode to "in table body" and abort these steps. */ - } elseif(in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { - $this->mode = self::IN_TBODY; - break; - - /* 8. If node is a caption element, then switch the insertion mode - to "in caption" and abort these steps. */ - } elseif($node->nodeName === 'caption') { - $this->mode = self::IN_CAPTION; - break; - - /* 9. If node is a colgroup element, then switch the insertion mode - to "in column group" and abort these steps. (innerHTML case) */ - } elseif($node->nodeName === 'colgroup') { - $this->mode = self::IN_CGROUP; - break; - - /* 10. If node is a table element, then switch the insertion mode - to "in table" and abort these steps. */ - } elseif($node->nodeName === 'table') { - $this->mode = self::IN_TABLE; - break; - - /* 11. If node is a head element, then switch the insertion mode - to "in body" ("in body"! not "in head"!) and abort these steps. - (innerHTML case) */ - } elseif($node->nodeName === 'head') { - $this->mode = self::IN_BODY; - break; - - /* 12. If node is a body element, then switch the insertion mode to - "in body" and abort these steps. */ - } elseif($node->nodeName === 'body') { - $this->mode = self::IN_BODY; - break; - - /* 13. If node is a frameset element, then switch the insertion - mode to "in frameset" and abort these steps. (innerHTML case) */ - } elseif($node->nodeName === 'frameset') { - $this->mode = self::IN_FRAME; - break; - - /* 14. If node is an html element, then: if the head element - pointer is null, switch the insertion mode to "before head", - otherwise, switch the insertion mode to "after head". In either - case, abort these steps. (innerHTML case) */ - } elseif($node->nodeName === 'html') { - $this->mode = ($this->head_pointer === null) - ? self::BEFOR_HEAD - : self::AFTER_HEAD; - - break; - - /* 15. If last is true, then set the insertion mode to "in body" - and abort these steps. (innerHTML case) */ - } elseif($last) { - $this->mode = self::IN_BODY; - break; - } - } - } - - private function closeCell() { - /* If the stack of open elements has a td or th element in table scope, - then act as if an end tag token with that tag name had been seen. */ - foreach(array('td', 'th') as $cell) { - if($this->elementInScope($cell, true)) { - $this->inCell(array( - 'name' => $cell, - 'type' => HTML5::ENDTAG - )); - - break; - } - } - } - - public function save() { - return $this->dom; - } -} -?> diff --git a/library/HTMLPurifier/Strategy/FixNesting.php b/library/HTMLPurifier/Strategy/FixNesting.php deleted file mode 100644 index f81802391b..0000000000 --- a/library/HTMLPurifier/Strategy/FixNesting.php +++ /dev/null @@ -1,328 +0,0 @@ -<?php - -/** - * Takes a well formed list of tokens and fixes their nesting. - * - * HTML elements dictate which elements are allowed to be their children, - * for example, you can't have a p tag in a span tag. Other elements have - * much more rigorous definitions: tables, for instance, require a specific - * order for their elements. There are also constraints not expressible by - * document type definitions, such as the chameleon nature of ins/del - * tags and global child exclusions. - * - * The first major objective of this strategy is to iterate through all the - * nodes (not tokens) of the list of tokens and determine whether or not - * their children conform to the element's definition. If they do not, the - * child definition may optionally supply an amended list of elements that - * is valid or require that the entire node be deleted (and the previous - * node rescanned). - * - * The second objective is to ensure that explicitly excluded elements of - * an element do not appear in its children. Code that accomplishes this - * task is pervasive through the strategy, though the two are distinct tasks - * and could, theoretically, be seperated (although it's not recommended). - * - * @note Whether or not unrecognized children are silently dropped or - * translated into text depends on the child definitions. - * - * @todo Enable nodes to be bubbled out of the structure. - */ - -class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy -{ - - public function execute($tokens, $config, $context) { - //####################################################################// - // Pre-processing - - // get a copy of the HTML definition - $definition = $config->getHTMLDefinition(); - - // insert implicit "parent" node, will be removed at end. - // DEFINITION CALL - $parent_name = $definition->info_parent; - array_unshift($tokens, new HTMLPurifier_Token_Start($parent_name)); - $tokens[] = new HTMLPurifier_Token_End($parent_name); - - // setup the context variable 'IsInline', for chameleon processing - // is 'false' when we are not inline, 'true' when it must always - // be inline, and an integer when it is inline for a certain - // branch of the document tree - $is_inline = $definition->info_parent_def->descendants_are_inline; - $context->register('IsInline', $is_inline); - - // setup error collector - $e =& $context->get('ErrorCollector', true); - - //####################################################################// - // Loop initialization - - // stack that contains the indexes of all parents, - // $stack[count($stack)-1] being the current parent - $stack = array(); - - // stack that contains all elements that are excluded - // it is organized by parent elements, similar to $stack, - // but it is only populated when an element with exclusions is - // processed, i.e. there won't be empty exclusions. - $exclude_stack = array(); - - // variable that contains the start token while we are processing - // nodes. This enables error reporting to do its job - $start_token = false; - $context->register('CurrentToken', $start_token); - - //####################################################################// - // Loop - - // iterate through all start nodes. Determining the start node - // is complicated so it has been omitted from the loop construct - for ($i = 0, $size = count($tokens) ; $i < $size; ) { - - //################################################################// - // Gather information on children - - // child token accumulator - $child_tokens = array(); - - // scroll to the end of this node, report number, and collect - // all children - for ($j = $i, $depth = 0; ; $j++) { - if ($tokens[$j] instanceof HTMLPurifier_Token_Start) { - $depth++; - // skip token assignment on first iteration, this is the - // token we currently are on - if ($depth == 1) continue; - } elseif ($tokens[$j] instanceof HTMLPurifier_Token_End) { - $depth--; - // skip token assignment on last iteration, this is the - // end token of the token we're currently on - if ($depth == 0) break; - } - $child_tokens[] = $tokens[$j]; - } - - // $i is index of start token - // $j is index of end token - - $start_token = $tokens[$i]; // to make token available via CurrentToken - - //################################################################// - // Gather information on parent - - // calculate parent information - if ($count = count($stack)) { - $parent_index = $stack[$count-1]; - $parent_name = $tokens[$parent_index]->name; - if ($parent_index == 0) { - $parent_def = $definition->info_parent_def; - } else { - $parent_def = $definition->info[$parent_name]; - } - } else { - // processing as if the parent were the "root" node - // unknown info, it won't be used anyway, in the future, - // we may want to enforce one element only (this is - // necessary for HTML Purifier to clean entire documents - $parent_index = $parent_name = $parent_def = null; - } - - // calculate context - if ($is_inline === false) { - // check if conditions make it inline - if (!empty($parent_def) && $parent_def->descendants_are_inline) { - $is_inline = $count - 1; - } - } else { - // check if we're out of inline - if ($count === $is_inline) { - $is_inline = false; - } - } - - //################################################################// - // Determine whether element is explicitly excluded SGML-style - - // determine whether or not element is excluded by checking all - // parent exclusions. The array should not be very large, two - // elements at most. - $excluded = false; - if (!empty($exclude_stack)) { - foreach ($exclude_stack as $lookup) { - if (isset($lookup[$tokens[$i]->name])) { - $excluded = true; - // no need to continue processing - break; - } - } - } - - //################################################################// - // Perform child validation - - if ($excluded) { - // there is an exclusion, remove the entire node - $result = false; - $excludes = array(); // not used, but good to initialize anyway - } else { - // DEFINITION CALL - if ($i === 0) { - // special processing for the first node - $def = $definition->info_parent_def; - } else { - $def = $definition->info[$tokens[$i]->name]; - - } - - if (!empty($def->child)) { - // have DTD child def validate children - $result = $def->child->validateChildren( - $child_tokens, $config, $context); - } else { - // weird, no child definition, get rid of everything - $result = false; - } - - // determine whether or not this element has any exclusions - $excludes = $def->excludes; - } - - // $result is now a bool or array - - //################################################################// - // Process result by interpreting $result - - if ($result === true || $child_tokens === $result) { - // leave the node as is - - // register start token as a parental node start - $stack[] = $i; - - // register exclusions if there are any - if (!empty($excludes)) $exclude_stack[] = $excludes; - - // move cursor to next possible start node - $i++; - - } elseif($result === false) { - // remove entire node - - if ($e) { - if ($excluded) { - $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); - } else { - $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); - } - } - - // calculate length of inner tokens and current tokens - $length = $j - $i + 1; - - // perform removal - array_splice($tokens, $i, $length); - - // update size - $size -= $length; - - // there is no start token to register, - // current node is now the next possible start node - // unless it turns out that we need to do a double-check - - // this is a rought heuristic that covers 100% of HTML's - // cases and 99% of all other cases. A child definition - // that would be tricked by this would be something like: - // ( | a b c) where it's all or nothing. Fortunately, - // our current implementation claims that that case would - // not allow empty, even if it did - if (!$parent_def->child->allow_empty) { - // we need to do a double-check - $i = $parent_index; - array_pop($stack); - } - - // PROJECTED OPTIMIZATION: Process all children elements before - // reprocessing parent node. - - } else { - // replace node with $result - - // calculate length of inner tokens - $length = $j - $i - 1; - - if ($e) { - if (empty($result) && $length) { - $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); - } else { - $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); - } - } - - // perform replacement - array_splice($tokens, $i + 1, $length, $result); - - // update size - $size -= $length; - $size += count($result); - - // register start token as a parental node start - $stack[] = $i; - - // register exclusions if there are any - if (!empty($excludes)) $exclude_stack[] = $excludes; - - // move cursor to next possible start node - $i++; - - } - - //################################################################// - // Scroll to next start node - - // We assume, at this point, that $i is the index of the token - // that is the first possible new start point for a node. - - // Test if the token indeed is a start tag, if not, move forward - // and test again. - $size = count($tokens); - while ($i < $size and !$tokens[$i] instanceof HTMLPurifier_Token_Start) { - if ($tokens[$i] instanceof HTMLPurifier_Token_End) { - // pop a token index off the stack if we ended a node - array_pop($stack); - // pop an exclusion lookup off exclusion stack if - // we ended node and that node had exclusions - if ($i == 0 || $i == $size - 1) { - // use specialized var if it's the super-parent - $s_excludes = $definition->info_parent_def->excludes; - } else { - $s_excludes = $definition->info[$tokens[$i]->name]->excludes; - } - if ($s_excludes) { - array_pop($exclude_stack); - } - } - $i++; - } - - } - - //####################################################################// - // Post-processing - - // remove implicit parent tokens at the beginning and end - array_shift($tokens); - array_pop($tokens); - - // remove context variables - $context->destroy('IsInline'); - $context->destroy('CurrentToken'); - - //####################################################################// - // Return - - return $tokens; - - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Token.php b/library/HTMLPurifier/Token.php deleted file mode 100644 index 7900e6cb10..0000000000 --- a/library/HTMLPurifier/Token.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php - -/** - * Abstract base token class that all others inherit from. - */ -class HTMLPurifier_Token { - public $line; /**< Line number node was on in source document. Null if unknown. */ - public $col; /**< Column of line node was on in source document. Null if unknown. */ - - /** - * Lookup array of processing that this token is exempt from. - * Currently, valid values are "ValidateAttributes" and - * "MakeWellFormed_TagClosedError" - */ - public $armor = array(); - - /** - * Used during MakeWellFormed. - */ - public $skip; - public $rewind; - public $carryover; - - public function __get($n) { - if ($n === 'type') { - trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE); - switch (get_class($this)) { - case 'HTMLPurifier_Token_Start': return 'start'; - case 'HTMLPurifier_Token_Empty': return 'empty'; - case 'HTMLPurifier_Token_End': return 'end'; - case 'HTMLPurifier_Token_Text': return 'text'; - case 'HTMLPurifier_Token_Comment': return 'comment'; - default: return null; - } - } - } - - /** - * Sets the position of the token in the source document. - */ - public function position($l = null, $c = null) { - $this->line = $l; - $this->col = $c; - } - - /** - * Convenience function for DirectLex settings line/col position. - */ - public function rawPosition($l, $c) { - if ($c === -1) $l++; - $this->line = $l; - $this->col = $c; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Token/Comment.php b/library/HTMLPurifier/Token/Comment.php deleted file mode 100644 index dc6bdcabb8..0000000000 --- a/library/HTMLPurifier/Token/Comment.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php - -/** - * Concrete comment token class. Generally will be ignored. - */ -class HTMLPurifier_Token_Comment extends HTMLPurifier_Token -{ - public $data; /**< Character data within comment. */ - public $is_whitespace = true; - /** - * Transparent constructor. - * - * @param $data String comment data. - */ - public function __construct($data, $line = null, $col = null) { - $this->data = $data; - $this->line = $line; - $this->col = $col; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/TokenFactory.php b/library/HTMLPurifier/TokenFactory.php deleted file mode 100644 index 7cf48fb41c..0000000000 --- a/library/HTMLPurifier/TokenFactory.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php - -/** - * Factory for token generation. - * - * @note Doing some benchmarking indicates that the new operator is much - * slower than the clone operator (even discounting the cost of the - * constructor). This class is for that optimization. - * Other then that, there's not much point as we don't - * maintain parallel HTMLPurifier_Token hierarchies (the main reason why - * you'd want to use an abstract factory). - * @todo Port DirectLex to use this - */ -class HTMLPurifier_TokenFactory -{ - - /** - * Prototypes that will be cloned. - * @private - */ - // p stands for prototype - private $p_start, $p_end, $p_empty, $p_text, $p_comment; - - /** - * Generates blank prototypes for cloning. - */ - public function __construct() { - $this->p_start = new HTMLPurifier_Token_Start('', array()); - $this->p_end = new HTMLPurifier_Token_End(''); - $this->p_empty = new HTMLPurifier_Token_Empty('', array()); - $this->p_text = new HTMLPurifier_Token_Text(''); - $this->p_comment= new HTMLPurifier_Token_Comment(''); - } - - /** - * Creates a HTMLPurifier_Token_Start. - * @param $name Tag name - * @param $attr Associative array of attributes - * @return Generated HTMLPurifier_Token_Start - */ - public function createStart($name, $attr = array()) { - $p = clone $this->p_start; - $p->__construct($name, $attr); - return $p; - } - - /** - * Creates a HTMLPurifier_Token_End. - * @param $name Tag name - * @return Generated HTMLPurifier_Token_End - */ - public function createEnd($name) { - $p = clone $this->p_end; - $p->__construct($name); - return $p; - } - - /** - * Creates a HTMLPurifier_Token_Empty. - * @param $name Tag name - * @param $attr Associative array of attributes - * @return Generated HTMLPurifier_Token_Empty - */ - public function createEmpty($name, $attr = array()) { - $p = clone $this->p_empty; - $p->__construct($name, $attr); - return $p; - } - - /** - * Creates a HTMLPurifier_Token_Text. - * @param $data Data of text token - * @return Generated HTMLPurifier_Token_Text - */ - public function createText($data) { - $p = clone $this->p_text; - $p->__construct($data); - return $p; - } - - /** - * Creates a HTMLPurifier_Token_Comment. - * @param $data Data of comment token - * @return Generated HTMLPurifier_Token_Comment - */ - public function createComment($data) { - $p = clone $this->p_comment; - $p->__construct($data); - return $p; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URI.php b/library/HTMLPurifier/URI.php deleted file mode 100644 index 8b50d0d18d..0000000000 --- a/library/HTMLPurifier/URI.php +++ /dev/null @@ -1,173 +0,0 @@ -<?php - -/** - * HTML Purifier's internal representation of a URI. - * @note - * Internal data-structures are completely escaped. If the data needs - * to be used in a non-URI context (which is very unlikely), be sure - * to decode it first. The URI may not necessarily be well-formed until - * validate() is called. - */ -class HTMLPurifier_URI -{ - - public $scheme, $userinfo, $host, $port, $path, $query, $fragment; - - /** - * @note Automatically normalizes scheme and port - */ - public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment) { - $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme); - $this->userinfo = $userinfo; - $this->host = $host; - $this->port = is_null($port) ? $port : (int) $port; - $this->path = $path; - $this->query = $query; - $this->fragment = $fragment; - } - - /** - * Retrieves a scheme object corresponding to the URI's scheme/default - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @return Scheme object appropriate for validating this URI - */ - public function getSchemeObj($config, $context) { - $registry = HTMLPurifier_URISchemeRegistry::instance(); - if ($this->scheme !== null) { - $scheme_obj = $registry->getScheme($this->scheme, $config, $context); - if (!$scheme_obj) return false; // invalid scheme, clean it out - } else { - // no scheme: retrieve the default one - $def = $config->getDefinition('URI'); - $scheme_obj = $registry->getScheme($def->defaultScheme, $config, $context); - if (!$scheme_obj) { - // something funky happened to the default scheme object - trigger_error( - 'Default scheme object "' . $def->defaultScheme . '" was not readable', - E_USER_WARNING - ); - return false; - } - } - return $scheme_obj; - } - - /** - * Generic validation method applicable for all schemes. May modify - * this URI in order to get it into a compliant form. - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @return True if validation/filtering succeeds, false if failure - */ - public function validate($config, $context) { - - // ABNF definitions from RFC 3986 - $chars_sub_delims = '!$&\'()*+,;='; - $chars_gen_delims = ':/?#[]@'; - $chars_pchar = $chars_sub_delims . ':@'; - - // validate scheme (MUST BE FIRST!) - if (!is_null($this->scheme) && is_null($this->host)) { - $def = $config->getDefinition('URI'); - if ($def->defaultScheme === $this->scheme) { - $this->scheme = null; - } - } - - // validate host - if (!is_null($this->host)) { - $host_def = new HTMLPurifier_AttrDef_URI_Host(); - $this->host = $host_def->validate($this->host, $config, $context); - if ($this->host === false) $this->host = null; - } - - // validate username - if (!is_null($this->userinfo)) { - $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':'); - $this->userinfo = $encoder->encode($this->userinfo); - } - - // validate port - if (!is_null($this->port)) { - if ($this->port < 1 || $this->port > 65535) $this->port = null; - } - - // validate path - $path_parts = array(); - $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/'); - if (!is_null($this->host)) { - // path-abempty (hier and relative) - $this->path = $segments_encoder->encode($this->path); - } elseif ($this->path !== '' && $this->path[0] === '/') { - // path-absolute (hier and relative) - if (strlen($this->path) >= 2 && $this->path[1] === '/') { - // This shouldn't ever happen! - $this->path = ''; - } else { - $this->path = $segments_encoder->encode($this->path); - } - } elseif (!is_null($this->scheme) && $this->path !== '') { - // path-rootless (hier) - // Short circuit evaluation means we don't need to check nz - $this->path = $segments_encoder->encode($this->path); - } elseif (is_null($this->scheme) && $this->path !== '') { - // path-noscheme (relative) - // (once again, not checking nz) - $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@'); - $c = strpos($this->path, '/'); - if ($c !== false) { - $this->path = - $segment_nc_encoder->encode(substr($this->path, 0, $c)) . - $segments_encoder->encode(substr($this->path, $c)); - } else { - $this->path = $segment_nc_encoder->encode($this->path); - } - } else { - // path-empty (hier and relative) - $this->path = ''; // just to be safe - } - - // qf = query and fragment - $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?'); - - if (!is_null($this->query)) { - $this->query = $qf_encoder->encode($this->query); - } - - if (!is_null($this->fragment)) { - $this->fragment = $qf_encoder->encode($this->fragment); - } - - return true; - - } - - /** - * Convert URI back to string - * @return String URI appropriate for output - */ - public function toString() { - // reconstruct authority - $authority = null; - if (!is_null($this->host)) { - $authority = ''; - if(!is_null($this->userinfo)) $authority .= $this->userinfo . '@'; - $authority .= $this->host; - if(!is_null($this->port)) $authority .= ':' . $this->port; - } - - // reconstruct the result - $result = ''; - if (!is_null($this->scheme)) $result .= $this->scheme . ':'; - if (!is_null($authority)) $result .= '//' . $authority; - $result .= $this->path; - if (!is_null($this->query)) $result .= '?' . $this->query; - if (!is_null($this->fragment)) $result .= '#' . $this->fragment; - - return $result; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIFilter.php b/library/HTMLPurifier/URIFilter.php deleted file mode 100644 index c116f93dff..0000000000 --- a/library/HTMLPurifier/URIFilter.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -/** - * Chainable filters for custom URI processing. - * - * These filters can perform custom actions on a URI filter object, - * including transformation or blacklisting. - * - * @warning This filter is called before scheme object validation occurs. - * Make sure, if you require a specific scheme object, you - * you check that it exists. This allows filters to convert - * proprietary URI schemes into regular ones. - */ -abstract class HTMLPurifier_URIFilter -{ - - /** - * Unique identifier of filter - */ - public $name; - - /** - * True if this filter should be run after scheme validation. - */ - public $post = false; - - /** - * Performs initialization for the filter - */ - public function prepare($config) {return true;} - - /** - * Filter a URI object - * @param $uri Reference to URI object variable - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @return bool Whether or not to continue processing: false indicates - * URL is no good, true indicates continue processing. Note that - * all changes are committed directly on the URI object - */ - abstract public function filter(&$uri, $config, $context); - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIFilter/DisableExternal.php b/library/HTMLPurifier/URIFilter/DisableExternal.php deleted file mode 100644 index d8a39a5011..0000000000 --- a/library/HTMLPurifier/URIFilter/DisableExternal.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php - -class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter -{ - public $name = 'DisableExternal'; - protected $ourHostParts = false; - public function prepare($config) { - $our_host = $config->getDefinition('URI')->host; - if ($our_host !== null) $this->ourHostParts = array_reverse(explode('.', $our_host)); - } - public function filter(&$uri, $config, $context) { - if (is_null($uri->host)) return true; - if ($this->ourHostParts === false) return false; - $host_parts = array_reverse(explode('.', $uri->host)); - foreach ($this->ourHostParts as $i => $x) { - if (!isset($host_parts[$i])) return false; - if ($host_parts[$i] != $this->ourHostParts[$i]) return false; - } - return true; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/library/HTMLPurifier/URIFilter/DisableExternalResources.php deleted file mode 100644 index 881abc43cf..0000000000 --- a/library/HTMLPurifier/URIFilter/DisableExternalResources.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php - -class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal -{ - public $name = 'DisableExternalResources'; - public function filter(&$uri, $config, $context) { - if (!$context->get('EmbeddedURI', true)) return true; - return parent::filter($uri, $config, $context); - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIFilter/HostBlacklist.php b/library/HTMLPurifier/URIFilter/HostBlacklist.php deleted file mode 100644 index 045aa0992c..0000000000 --- a/library/HTMLPurifier/URIFilter/HostBlacklist.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php - -class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter -{ - public $name = 'HostBlacklist'; - protected $blacklist = array(); - public function prepare($config) { - $this->blacklist = $config->get('URI.HostBlacklist'); - return true; - } - public function filter(&$uri, $config, $context) { - foreach($this->blacklist as $blacklisted_host_fragment) { - if (strpos($uri->host, $blacklisted_host_fragment) !== false) { - return false; - } - } - return true; - } -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIFilter/Munge.php b/library/HTMLPurifier/URIFilter/Munge.php deleted file mode 100644 index efa10a6458..0000000000 --- a/library/HTMLPurifier/URIFilter/Munge.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter -{ - public $name = 'Munge'; - public $post = true; - private $target, $parser, $doEmbed, $secretKey; - - protected $replace = array(); - - public function prepare($config) { - $this->target = $config->get('URI.' . $this->name); - $this->parser = new HTMLPurifier_URIParser(); - $this->doEmbed = $config->get('URI.MungeResources'); - $this->secretKey = $config->get('URI.MungeSecretKey'); - return true; - } - public function filter(&$uri, $config, $context) { - if ($context->get('EmbeddedURI', true) && !$this->doEmbed) return true; - - $scheme_obj = $uri->getSchemeObj($config, $context); - if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it - if (is_null($uri->host) || empty($scheme_obj->browsable)) { - return true; - } - // don't redirect if target host is our host - if ($uri->host === $config->getDefinition('URI')->host) { - return true; - } - - $this->makeReplace($uri, $config, $context); - $this->replace = array_map('rawurlencode', $this->replace); - - $new_uri = strtr($this->target, $this->replace); - $new_uri = $this->parser->parse($new_uri); - // don't redirect if the target host is the same as the - // starting host - if ($uri->host === $new_uri->host) return true; - $uri = $new_uri; // overwrite - return true; - } - - protected function makeReplace($uri, $config, $context) { - $string = $uri->toString(); - // always available - $this->replace['%s'] = $string; - $this->replace['%r'] = $context->get('EmbeddedURI', true); - $token = $context->get('CurrentToken', true); - $this->replace['%n'] = $token ? $token->name : null; - $this->replace['%m'] = $context->get('CurrentAttr', true); - $this->replace['%p'] = $context->get('CurrentCSSProperty', true); - // not always available - if ($this->secretKey) $this->replace['%t'] = sha1($this->secretKey . ':' . $string); - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme.php b/library/HTMLPurifier/URIScheme.php deleted file mode 100644 index 039710fd15..0000000000 --- a/library/HTMLPurifier/URIScheme.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php - -/** - * Validator for the components of a URI for a specific scheme - */ -class HTMLPurifier_URIScheme -{ - - /** - * Scheme's default port (integer) - */ - public $default_port = null; - - /** - * Whether or not URIs of this schem are locatable by a browser - * http and ftp are accessible, while mailto and news are not. - */ - public $browsable = false; - - /** - * Whether or not the URI always uses <hier_part>, resolves edge cases - * with making relative URIs absolute - */ - public $hierarchical = false; - - /** - * Validates the components of a URI - * @note This implementation should be called by children if they define - * a default port, as it does port processing. - * @param $uri Instance of HTMLPurifier_URI - * @param $config HTMLPurifier_Config object - * @param $context HTMLPurifier_Context object - * @return Bool success or failure - */ - public function validate(&$uri, $config, $context) { - if ($this->default_port == $uri->port) $uri->port = null; - return true; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme/news.php b/library/HTMLPurifier/URIScheme/news.php deleted file mode 100644 index f5f54f4f56..0000000000 --- a/library/HTMLPurifier/URIScheme/news.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php - -/** - * Validates news (Usenet) as defined by generic RFC 1738 - */ -class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme { - - public $browsable = false; - - public function validate(&$uri, $config, $context) { - parent::validate($uri, $config, $context); - $uri->userinfo = null; - $uri->host = null; - $uri->port = null; - $uri->query = null; - // typecode check needed on path - return true; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme/nntp.php b/library/HTMLPurifier/URIScheme/nntp.php deleted file mode 100644 index 5bf93ea784..0000000000 --- a/library/HTMLPurifier/URIScheme/nntp.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -/** - * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738 - */ -class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme { - - public $default_port = 119; - public $browsable = false; - - public function validate(&$uri, $config, $context) { - parent::validate($uri, $config, $context); - $uri->userinfo = null; - $uri->query = null; - return true; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/VarParser.php b/library/HTMLPurifier/VarParser.php deleted file mode 100644 index 68e72ae869..0000000000 --- a/library/HTMLPurifier/VarParser.php +++ /dev/null @@ -1,154 +0,0 @@ -<?php - -/** - * Parses string representations into their corresponding native PHP - * variable type. The base implementation does a simple type-check. - */ -class HTMLPurifier_VarParser -{ - - const STRING = 1; - const ISTRING = 2; - const TEXT = 3; - const ITEXT = 4; - const INT = 5; - const FLOAT = 6; - const BOOL = 7; - const LOOKUP = 8; - const ALIST = 9; - const HASH = 10; - const MIXED = 11; - - /** - * Lookup table of allowed types. Mainly for backwards compatibility, but - * also convenient for transforming string type names to the integer constants. - */ - static public $types = array( - 'string' => self::STRING, - 'istring' => self::ISTRING, - 'text' => self::TEXT, - 'itext' => self::ITEXT, - 'int' => self::INT, - 'float' => self::FLOAT, - 'bool' => self::BOOL, - 'lookup' => self::LOOKUP, - 'list' => self::ALIST, - 'hash' => self::HASH, - 'mixed' => self::MIXED - ); - - /** - * Lookup table of types that are string, and can have aliases or - * allowed value lists. - */ - static public $stringTypes = array( - self::STRING => true, - self::ISTRING => true, - self::TEXT => true, - self::ITEXT => true, - ); - - /** - * Validate a variable according to type. Throws - * HTMLPurifier_VarParserException if invalid. - * It may return NULL as a valid type if $allow_null is true. - * - * @param $var Variable to validate - * @param $type Type of variable, see HTMLPurifier_VarParser->types - * @param $allow_null Whether or not to permit null as a value - * @return Validated and type-coerced variable - */ - final public function parse($var, $type, $allow_null = false) { - if (is_string($type)) { - if (!isset(HTMLPurifier_VarParser::$types[$type])) { - throw new HTMLPurifier_VarParserException("Invalid type '$type'"); - } else { - $type = HTMLPurifier_VarParser::$types[$type]; - } - } - $var = $this->parseImplementation($var, $type, $allow_null); - if ($allow_null && $var === null) return null; - // These are basic checks, to make sure nothing horribly wrong - // happened in our implementations. - switch ($type) { - case (self::STRING): - case (self::ISTRING): - case (self::TEXT): - case (self::ITEXT): - if (!is_string($var)) break; - if ($type == self::ISTRING || $type == self::ITEXT) $var = strtolower($var); - return $var; - case (self::INT): - if (!is_int($var)) break; - return $var; - case (self::FLOAT): - if (!is_float($var)) break; - return $var; - case (self::BOOL): - if (!is_bool($var)) break; - return $var; - case (self::LOOKUP): - case (self::ALIST): - case (self::HASH): - if (!is_array($var)) break; - if ($type === self::LOOKUP) { - foreach ($var as $k) if ($k !== true) $this->error('Lookup table contains value other than true'); - } elseif ($type === self::ALIST) { - $keys = array_keys($var); - if (array_keys($keys) !== $keys) $this->error('Indices for list are not uniform'); - } - return $var; - case (self::MIXED): - return $var; - default: - $this->errorInconsistent(get_class($this), $type); - } - $this->errorGeneric($var, $type); - } - - /** - * Actually implements the parsing. Base implementation is to not - * do anything to $var. Subclasses should overload this! - */ - protected function parseImplementation($var, $type, $allow_null) { - return $var; - } - - /** - * Throws an exception. - */ - protected function error($msg) { - throw new HTMLPurifier_VarParserException($msg); - } - - /** - * Throws an inconsistency exception. - * @note This should not ever be called. It would be called if we - * extend the allowed values of HTMLPurifier_VarParser without - * updating subclasses. - */ - protected function errorInconsistent($class, $type) { - throw new HTMLPurifier_Exception("Inconsistency in $class: ".HTMLPurifier_VarParser::getTypeName($type)." not implemented"); - } - - /** - * Generic error for if a type didn't work. - */ - protected function errorGeneric($var, $type) { - $vtype = gettype($var); - $this->error("Expected type ".HTMLPurifier_VarParser::getTypeName($type).", got $vtype"); - } - - static public function getTypeName($type) { - static $lookup; - if (!$lookup) { - // Lazy load the alternative lookup table - $lookup = array_flip(HTMLPurifier_VarParser::$types); - } - if (!isset($lookup[$type])) return 'unknown'; - return $lookup[$type]; - } - -} - -// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/CREDITS b/library/ezyang/htmlpurifier/CREDITS new file mode 100644 index 0000000000..7921b45af7 --- /dev/null +++ b/library/ezyang/htmlpurifier/CREDITS @@ -0,0 +1,9 @@ + +CREDITS + +Almost everything written by Edward Z. Yang (Ambush Commander). Lots of thanks +to the DevNetwork Community for their help (see docs/ref-devnetwork.html for +more details), Feyd especially (namely IPv6 and optimization). Thanks to RSnake +for letting me package his fantastic XSS cheatsheet for a smoketest. + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/INSTALL b/library/ezyang/htmlpurifier/INSTALL new file mode 100644 index 0000000000..677c04aa04 --- /dev/null +++ b/library/ezyang/htmlpurifier/INSTALL @@ -0,0 +1,374 @@ + +Install + How to install HTML Purifier + +HTML Purifier is designed to run out of the box, so actually using the +library is extremely easy. (Although... if you were looking for a +step-by-step installation GUI, you've downloaded the wrong software!) + +While the impatient can get going immediately with some of the sample +code at the bottom of this library, it's well worth reading this entire +document--most of the other documentation assumes that you are familiar +with these contents. + + +--------------------------------------------------------------------------- +1. Compatibility + +HTML Purifier is PHP 5 only, and is actively tested from PHP 5.0.5 and +up. It has no core dependencies with other libraries. PHP +4 support was deprecated on December 31, 2007 with HTML Purifier 3.0.0. +HTML Purifier is not compatible with zend.ze1_compatibility_mode. + +These optional extensions can enhance the capabilities of HTML Purifier: + + * iconv : Converts text to and from non-UTF-8 encodings + * bcmath : Used for unit conversion and imagecrash protection + * tidy : Used for pretty-printing HTML + +These optional libraries can enhance the capabilities of HTML Purifier: + + * CSSTidy : Clean CSS stylesheets using %Core.ExtractStyleBlocks + * Net_IDNA2 (PEAR) : IRI support using %Core.EnableIDNA + +--------------------------------------------------------------------------- +2. Reconnaissance + +A big plus of HTML Purifier is its inerrant support of standards, so +your web-pages should be standards-compliant. (They should also use +semantic markup, but that's another issue altogether, one HTML Purifier +cannot fix without reading your mind.) + +HTML Purifier can process these doctypes: + +* XHTML 1.0 Transitional (default) +* XHTML 1.0 Strict +* HTML 4.01 Transitional +* HTML 4.01 Strict +* XHTML 1.1 + +...and these character encodings: + +* UTF-8 (default) +* Any encoding iconv supports (with crippled internationalization support) + +These defaults reflect what my choices would be if I were authoring an +HTML document, however, what you choose depends on the nature of your +codebase. If you don't know what doctype you are using, you can determine +the doctype from this identifier at the top of your source code: + + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +...and the character encoding from this code: + + <meta http-equiv="Content-type" content="text/html;charset=ENCODING"> + +If the character encoding declaration is missing, STOP NOW, and +read 'docs/enduser-utf8.html' (web accessible at +http://htmlpurifier.org/docs/enduser-utf8.html). In fact, even if it is +present, read this document anyway, as many websites specify their +document's character encoding incorrectly. + + +--------------------------------------------------------------------------- +3. Including the library + +The procedure is quite simple: + + require_once '/path/to/library/HTMLPurifier.auto.php'; + +This will setup an autoloader, so the library's files are only included +when you use them. + +Only the contents in the library/ folder are necessary, so you can remove +everything else when using HTML Purifier in a production environment. + +If you installed HTML Purifier via PEAR, all you need to do is: + + require_once 'HTMLPurifier.auto.php'; + +Please note that the usual PEAR practice of including just the classes you +want will not work with HTML Purifier's autoloading scheme. + +Advanced users, read on; other users can skip to section 4. + +Autoload compatibility +---------------------- + + HTML Purifier attempts to be as smart as possible when registering an + autoloader, but there are some cases where you will need to change + your own code to accomodate HTML Purifier. These are those cases: + + PHP VERSION IS LESS THAN 5.1.2, AND YOU'VE DEFINED __autoload + Because spl_autoload_register() doesn't exist in early versions + of PHP 5, HTML Purifier has no way of adding itself to the autoload + stack. Modify your __autoload function to test + HTMLPurifier_Bootstrap::autoload($class) + + For example, suppose your autoload function looks like this: + + function __autoload($class) { + require str_replace('_', '/', $class) . '.php'; + return true; + } + + A modified version with HTML Purifier would look like this: + + function __autoload($class) { + if (HTMLPurifier_Bootstrap::autoload($class)) return true; + require str_replace('_', '/', $class) . '.php'; + return true; + } + + Note that there *is* some custom behavior in our autoloader; the + original autoloader in our example would work for 99% of the time, + but would fail when including language files. + + AN __autoload FUNCTION IS DECLARED AFTER OUR AUTOLOADER IS REGISTERED + spl_autoload_register() has the curious behavior of disabling + the existing __autoload() handler. Users need to explicitly + spl_autoload_register('__autoload'). Because we use SPL when it + is available, __autoload() will ALWAYS be disabled. If __autoload() + is declared before HTML Purifier is loaded, this is not a problem: + HTML Purifier will register the function for you. But if it is + declared afterwards, it will mysteriously not work. This + snippet of code (after your autoloader is defined) will fix it: + + spl_autoload_register('__autoload') + + Users should also be on guard if they use a version of PHP previous + to 5.1.2 without an autoloader--HTML Purifier will define __autoload() + for you, which can collide with an autoloader that was added by *you* + later. + + +For better performance +---------------------- + + Opcode caches, which greatly speed up PHP initialization for scripts + with large amounts of code (HTML Purifier included), don't like + autoloaders. We offer an include file that includes all of HTML Purifier's + files in one go in an opcode cache friendly manner: + + // If /path/to/library isn't already in your include path, uncomment + // the below line: + // require '/path/to/library/HTMLPurifier.path.php'; + + require 'HTMLPurifier.includes.php'; + + Optional components still need to be included--you'll know if you try to + use a feature and you get a class doesn't exists error! The autoloader + can be used in conjunction with this approach to catch classes that are + missing. Simply add this afterwards: + + require 'HTMLPurifier.autoload.php'; + +Standalone version +------------------ + + HTML Purifier has a standalone distribution; you can also generate + a standalone file from the full version by running the script + maintenance/generate-standalone.php . The standalone version has the + benefit of having most of its code in one file, so parsing is much + faster and the library is easier to manage. + + If HTMLPurifier.standalone.php exists in the library directory, you + can use it like this: + + require '/path/to/HTMLPurifier.standalone.php'; + + This is equivalent to including HTMLPurifier.includes.php, except that + the contents of standalone/ will be added to your path. To override this + behavior, specify a new HTMLPURIFIER_PREFIX where standalone files can + be found (usually, this will be one directory up, the "true" library + directory in full distributions). Don't forget to set your path too! + + The autoloader can be added to the end to ensure the classes are + loaded when necessary; otherwise you can manually include them. + To use the autoloader, use this: + + require 'HTMLPurifier.autoload.php'; + +For advanced users +------------------ + + HTMLPurifier.auto.php performs a number of operations that can be done + individually. These are: + + HTMLPurifier.path.php + Puts /path/to/library in the include path. For high performance, + this should be done in php.ini. + + HTMLPurifier.autoload.php + Registers our autoload handler HTMLPurifier_Bootstrap::autoload($class). + + You can do these operations by yourself--in fact, you must modify your own + autoload handler if you are using a version of PHP earlier than PHP 5.1.2 + (See "Autoload compatibility" above). + + +--------------------------------------------------------------------------- +4. Configuration + +HTML Purifier is designed to run out-of-the-box, but occasionally HTML +Purifier needs to be told what to do. If you answer no to any of these +questions, read on; otherwise, you can skip to the next section (or, if you're +into configuring things just for the heck of it, skip to 4.3). + +* Am I using UTF-8? +* Am I using XHTML 1.0 Transitional? + +If you answered no to any of these questions, instantiate a configuration +object and read on: + + $config = HTMLPurifier_Config::createDefault(); + + +4.1. Setting a different character encoding + +You really shouldn't use any other encoding except UTF-8, especially if you +plan to support multilingual websites (read section three for more details). +However, switching to UTF-8 is not always immediately feasible, so we can +adapt. + +HTML Purifier uses iconv to support other character encodings, as such, +any encoding that iconv supports <http://www.gnu.org/software/libiconv/> +HTML Purifier supports with this code: + + $config->set('Core.Encoding', /* put your encoding here */); + +An example usage for Latin-1 websites (the most common encoding for English +websites): + + $config->set('Core.Encoding', 'ISO-8859-1'); + +Note that HTML Purifier's support for non-Unicode encodings is crippled by the +fact that any character not supported by that encoding will be silently +dropped, EVEN if it is ampersand escaped. If you want to work around +this, you are welcome to read docs/enduser-utf8.html for a fix, +but please be cognizant of the issues the "solution" creates (for this +reason, I do not include the solution in this document). + + +4.2. Setting a different doctype + +For those of you using HTML 4.01 Transitional, you can disable +XHTML output like this: + + $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); + +Other supported doctypes include: + + * HTML 4.01 Strict + * HTML 4.01 Transitional + * XHTML 1.0 Strict + * XHTML 1.0 Transitional + * XHTML 1.1 + + +4.3. Other settings + +There are more configuration directives which can be read about +here: <http://htmlpurifier.org/live/configdoc/plain.html> They're a bit boring, +but they can help out for those of you who like to exert maximum control over +your code. Some of the more interesting ones are configurable at the +demo <http://htmlpurifier.org/demo.php> and are well worth looking into +for your own system. + +For example, you can fine tune allowed elements and attributes, convert +relative URLs to absolute ones, and even autoparagraph input text! These +are, respectively, %HTML.Allowed, %URI.MakeAbsolute and %URI.Base, and +%AutoFormat.AutoParagraph. The %Namespace.Directive naming convention +translates to: + + $config->set('Namespace.Directive', $value); + +E.g. + + $config->set('HTML.Allowed', 'p,b,a[href],i'); + $config->set('URI.Base', 'http://www.example.com'); + $config->set('URI.MakeAbsolute', true); + $config->set('AutoFormat.AutoParagraph', true); + + +--------------------------------------------------------------------------- +5. Caching + +HTML Purifier generates some cache files (generally one or two) to speed up +its execution. For maximum performance, make sure that +library/HTMLPurifier/DefinitionCache/Serializer is writeable by the webserver. + +If you are in the library/ folder of HTML Purifier, you can set the +appropriate permissions using: + + chmod -R 0755 HTMLPurifier/DefinitionCache/Serializer + +If the above command doesn't work, you may need to assign write permissions +to all. This may be necessary if your webserver runs as nobody, but is +not recommended since it means any other user can write files in the +directory. Use: + + chmod -R 0777 HTMLPurifier/DefinitionCache/Serializer + +You can also chmod files via your FTP client; this option +is usually accessible by right clicking the corresponding directory and +then selecting "chmod" or "file permissions". + +Starting with 2.0.1, HTML Purifier will generate friendly error messages +that will tell you exactly what you have to chmod the directory to, if in doubt, +follow its advice. + +If you are unable or unwilling to give write permissions to the cache +directory, you can either disable the cache (and suffer a performance +hit): + + $config->set('Core.DefinitionCache', null); + +Or move the cache directory somewhere else (no trailing slash): + + $config->set('Cache.SerializerPath', '/home/user/absolute/path'); + + +--------------------------------------------------------------------------- +6. Using the code + +The interface is mind-numbingly simple: + + $purifier = new HTMLPurifier($config); + $clean_html = $purifier->purify( $dirty_html ); + +That's it! For more examples, check out docs/examples/ (they aren't very +different though). Also, docs/enduser-slow.html gives advice on what to +do if HTML Purifier is slowing down your application. + + +--------------------------------------------------------------------------- +7. Quick install + +First, make sure library/HTMLPurifier/DefinitionCache/Serializer is +writable by the webserver (see Section 5: Caching above for details). +If your website is in UTF-8 and XHTML Transitional, use this code: + +<?php + require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php'; + + $config = HTMLPurifier_Config::createDefault(); + $purifier = new HTMLPurifier($config); + $clean_html = $purifier->purify($dirty_html); +?> + +If your website is in a different encoding or doctype, use this code: + +<?php + require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php'; + + $config = HTMLPurifier_Config::createDefault(); + $config->set('Core.Encoding', 'ISO-8859-1'); // replace with your encoding + $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); // replace with your doctype + $purifier = new HTMLPurifier($config); + + $clean_html = $purifier->purify($dirty_html); +?> + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/INSTALL.fr.utf8 b/library/ezyang/htmlpurifier/INSTALL.fr.utf8 new file mode 100644 index 0000000000..06e628cc96 --- /dev/null +++ b/library/ezyang/htmlpurifier/INSTALL.fr.utf8 @@ -0,0 +1,60 @@ + +Installation + Comment installer HTML Purifier + +Attention : Ce document est encodé en UTF-8, si les lettres avec des accents +ne s'affichent pas, prenez un meilleur éditeur de texte. + +L'installation de HTML Purifier est très simple, parce qu'il n'a pas besoin +de configuration. Pour les utilisateurs impatients, le code se trouve dans le +pied de page, mais je recommande de lire le document. + +1. Compatibilité + +HTML Purifier fonctionne avec PHP 5. PHP 5.0.5 est la dernière version testée. +Il ne dépend pas d'autres librairies. + +Les extensions optionnelles sont iconv (généralement déjà installée) et tidy +(répendue aussi). Si vous utilisez UTF-8 et que vous ne voulez pas l'indentation, +vous pouvez utiliser HTML Purifier sans ces extensions. + + +2. Inclure la librairie + +Quand vous devez l'utilisez, incluez le : + + require_once('/path/to/library/HTMLPurifier.auto.php'); + +Ne pas l'inclure si ce n'est pas nécessaire, car HTML Purifier est lourd. + +HTML Purifier utilise "autoload". Si vous avez défini la fonction __autoload, +vous devez ajouter cette fonction : + + spl_autoload_register('__autoload') + +Plus d'informations dans le document "INSTALL". + +3. Installation rapide + +Si votre site Web est en UTF-8 et XHTML Transitional, utilisez : + +<?php + require_once('/path/to/htmlpurifier/library/HTMLPurifier.auto.php'); + $purificateur = new HTMLPurifier(); + $html_propre = $purificateur->purify($html_a_purifier); +?> + +Sinon, utilisez : + +<?php + require_once('/path/to/html/purifier/library/HTMLPurifier.auto.load'); + $config = $HTMLPurifier_Config::createDefault(); + $config->set('Core', 'Encoding', 'ISO-8859-1'); //Remplacez par votre + encodage + $config->set('Core', 'XHTML', true); //Remplacer par false si HTML 4.01 + $purificateur = new HTMLPurifier($config); + $html_propre = $purificateur->purify($html_a_purifier); +?> + + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/LICENSE b/library/ezyang/htmlpurifier/LICENSE new file mode 100644 index 0000000000..8c88a20d45 --- /dev/null +++ b/library/ezyang/htmlpurifier/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/NEWS b/library/ezyang/htmlpurifier/NEWS new file mode 100644 index 0000000000..a9124af1a1 --- /dev/null +++ b/library/ezyang/htmlpurifier/NEWS @@ -0,0 +1,1094 @@ +NEWS ( CHANGELOG and HISTORY ) HTMLPurifier +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + += KEY ==================== + # Breaks back-compat + ! Feature + - Bugfix + + Sub-comment + . Internal change +========================== + +4.7.0, released 2015-08-04 +# opacity is now considered a "tricky" CSS property rather than a + proprietary one. +! %AutoFormat.RemoveEmpty.Predicate for specifying exactly when + an element should be considered "empty" (maybe preserve if it + has attributes), and modify iframe support so that the iframe + is removed if it is missing a src attribute. Thanks meeva for + reporting. +- Don't truncate upon encountering </div> when using DOMLex. Thanks + Myrto Christina for finally convincing me to fix this. +- Update YouTube filter for new code. +- Fix parsing of rgb() values with spaces in them for 'border' + attribute. +- Don't remove foo="" attributes if foo is a boolean attribute. Thanks + valME for reporting. + +4.6.0, released 2013-11-30 +# Secure URI munge hashing algorithm has changed to hash_hmac("sha256", $url, $secret). + Please update any verification scripts you may have. +# URI parsing algorithm was made more strict, so only prefixes which + looks like schemes will actually be schemes. Thanks + Michael Gusev <mgusev@sugarcrm.com> for fixing. +# %Core.EscapeInvalidChildren is no longer supported, and no longer does + anything. +! New directive %Core.AllowHostnameUnderscore which allows underscores + in hostnames. +- Eliminate quadratic behavior in DOMLex by using a proper queue. + Thanks Ole Laursen for noticing this. +- Rewritten MakeWellFormed/FixNesting implementation eliminates quadratic + behavior in the rest of the purificaiton pipeline. Thanks Chedburn + Networks for sponsoring this work. +- Made Linkify URL parser a bit less permissive, so that non-breaking + spaces and commas are not included as part of URL. Thanks nAS for fixing. +- Fix some bad interactions with %HTML.Allowed and injectors. Thanks + David Hirtz for reporting. +- Fix infinite loop in DirectLex. Thanks Ashar Javed (@soaj1664ashar) + for reporting. + +4.5.0, released 2013-02-17 +# Fix bug where stacked attribute transforms clobber each other; + this also means it's no longer possible to override attribute + transforms in later modules. No internal code was using this + but this may break some clients. +# We now use SHA-1 to identify cached definitions, instead of MD5. +! Support display:inline-block +! Support for more white-space CSS values. +! Permit underscores in font families +! Support for page-break-* CSS3 properties when proprietary properties + are enabled. +! New directive %Core.DisableExcludes; can be set to 'true' to turn off + SGML excludes checking. If HTML Purifier is removing too much text + and you don't care about full standards compliance, try setting this to + 'true'. +- Use prepend for SPL autoloading on PHP 5.3 and later. +- Fix bug with nofollow transform when pre-existing rel exists. +- Fix bug where background:url() always gets lower-cased + (but not background-image:url()) +- Fix bug with non lower-case color names in HTML +- Fix bug where data URI validation doesn't remove temporary files. + Thanks Javier Marín Ros <javiermarinros@gmail.com> for reporting. +- Don't remove certain empty tags on RemoveEmpty. + +4.4.0, released 2012-01-18 +# Removed PEARSax3 handler. +# URI.Munge now munges URIs inside the same host that go from https + to http. Reported by Neike Taika-Tessaro. +# Core.EscapeNonASCIICharacters now always transforms entities to + entities, even if target encoding is UTF-8. +# Tighten up selector validation in ExtractStyleBlocks. + Non-syntactically valid selectors are now rejected, along with + some of the more obscure ones such as attribute selectors, the + :lang pseudoselector, and anything not in CSS2.1. Furthermore, + ID and class selectors now work properly with the relevant + configuration attributes. Also, mute errors when parsing CSS + with CSS Tidy. Reported by Mario Heiderich and Norman Hippert. +! Added support for 'scope' attribute on tables. +! Added %HTML.TargetBlank, which adds target="blank" to all outgoing links. +! Properly handle sub-lists directly nested inside of lists in + a standards compliant way, by moving them into the preceding <li> +! Added %HTML.AllowedComments and %HTML.AllowedCommentsRegexp for + limited allowed comments in untrusted situations. +! Implement iframes, and allow them to be used in untrusted mode with + %HTML.SafeIframe and %URI.SafeIframeRegexp. Thanks Bradley M. Froehle + <brad.froehle@gmail.com> for submitting an initial version of the patch. +! The Forms module now works properly for transitional doctypes. +! Added support for internationalized domain names. You need the PEAR + Net_IDNA2 module to be in your path; if it is installed, ensure the + class can be loaded and then set %Core.EnableIDNA to true. +- Color keywords are now case insensitive. Thanks Yzmir Ramirez + <yramirez-htmlpurifier@adicio.com> for reporting. +- Explicitly initialize anonModule variable to null. +- Do not duplicate nofollow if already present. Thanks 178 + for reporting. +- Do not add nofollow if hostname matches our current host. Thanks 178 + for reporting, and Neike Taika-Tessaro for helping diagnose. +- Do not unset parser variable; this fixes intermittent serialization + problems. Thanks Neike Taika-Tessaro for reporting, bill + <10010tiger@gmail.com> for diagnosing. +- Fix iconv truncation bug, where non-UTF-8 target encodings see + output truncated after around 8000 characters. Thanks Jörg Ludwig + <joerg.ludwig@iserv.eu> for reporting. +- Fix broken table content model for XHTML1.1 (and also earlier + versions, although the W3C validator doesn't catch those violations). + Thanks GlitchMr <glitch.mr@gmail.com> for reporting. + +4.3.0, released 2011-03-27 +# Fixed broken caching of customized raw definitions, but requires an + API change. The old API still works but will emit a warning, + see http://htmlpurifier.org/docs/enduser-customize.html#optimized + for how to upgrade your code. +# Protect against Internet Explorer innerHTML behavior by specially + treating attributes with backticks but no angled brackets, quotes or + spaces. This constitutes a slight semantic change, which can be + reverted using %Output.FixInnerHTML. Reported by Neike Taika-Tessaro + and Mario Heiderich. +# Protect against cssText/innerHTML by restricting allowed characters + used in fonts further than mandated by the specification and encoding + some extra special characters in URLs. Reported by Neike + Taika-Tessaro and Mario Heiderich. +! Added %HTML.Nofollow to add rel="nofollow" to external links. +! More types of SPL autoloaders allowed on later versions of PHP. +! Implementations for position, top, left, right, bottom, z-index + when %CSS.Trusted is on. +! Add %Cache.SerializerPermissions option for custom serializer + directory/file permissions +! Fix longstanding bug in Flash support for non-IE browsers, and + allow more wmode attributes. +! Add %CSS.AllowedFonts to restrict permissible font names. +- Switch to an iterative traversal of the DOM, which prevents us + from running out of stack space for deeply nested documents. + Thanks Maxim Krizhanovsky for contributing a patch. +- Make removal of conditional IE comments ungreedy; thanks Bernd + for reporting. +- Escape CDATA before removing Internet Explorer comments. +- Fix removal of id attributes under certain conditions by ensuring + armor attributes are preserved when recreating tags. +- Check if schema.ser was corrupted. +- Check if zend.ze1_compatibility_mode is on, and error out if it is. + This safety check is only done for HTMLPurifier.auto.php; if you + are using standalone or the specialized includes files, you're + expected to know what you're doing. +- Stop repeatedly writing the cache file after I'm done customizing a + raw definition. Reported by ajh. +- Switch to using require_once in the Bootstrap to work around bad + interaction with Zend Debugger and APC. Reported by Antonio Parraga. +- Fix URI handling when hostname is missing but scheme is present. + Reported by Neike Taika-Tessaro. +- Fix missing numeric entities on DirectLex; thanks Neike Taika-Tessaro + for reporting. +- Fix harmless notice from indexing into empty string. Thanks Matthijs + Kooijman <matthijs@stdin.nl> for reporting. +- Don't autoclose no parent elements are able to support the element + that triggered the autoclose. In particular fixes strange behavior + of stray <li> tags. Thanks pkuliga@gmail.com for reporting and + Neike Taika-Tessaro <pinkgothic@gmail.com> for debugging assistance. + +4.2.0, released 2010-09-15 +! Added %Core.RemoveProcessingInstructions, which lets you remove + <? ... ?> statements. +! Added %URI.DisableResources functionality; the directive originally + did nothing. Thanks David Rothstein for reporting. +! Add documentation about configuration directive types. +! Add %CSS.ForbiddenProperties configuration directive. +! Add %HTML.FlashAllowFullScreen to permit embedded Flash objects + to utilize full-screen mode. +! Add optional support for the <code>file</code> URI scheme, enable + by explicitly setting %URI.AllowedSchemes. +! Add %Core.NormalizeNewlines options to allow turning off newline + normalization. +- Fix improper handling of Internet Explorer conditional comments + by parser. Thanks zmonteca for reporting. +- Fix missing attributes bug when running on Mac Snow Leopard and APC. + Thanks sidepodcast for the fix. +- Warn if an element is allowed, but an attribute it requires is + not allowed. + +4.1.1, released 2010-05-31 +- Fix undefined index warnings in maintenance scripts. +- Fix bug in DirectLex for parsing elements with a single attribute + with entities. +- Rewrite CSS output logic for font-family and url(). Thanks Mario + Heiderich <mario.heiderich@googlemail.com> for reporting and Takeshi + Terada <t-terada@violet.plala.or.jp> for suggesting the fix. +- Emit an error for CollectErrors if a body is extracted +- Fix bug where in background-position for center keyword handling. +- Fix infinite loop when a wrapper element is inserted in a context + where it's not allowed. Thanks Lars <lars@renoz.dk> for reporting. +- Remove +x bit and shebang from index.php; only supported mode is to + explicitly call it with php. +- Make test script less chatty when log_errors is on. + +4.1.0, released 2010-04-26 +! Support proprietary height attribute on table element +! Support YouTube slideshows that contain /cp/ in their URL. +! Support for data: URI scheme; not enabled by default, add it using + %URI.AllowedSchemes +! Support flashvars when using %HTML.SafeObject and %HTML.SafeEmbed. +! Support for Internet Explorer compatibility with %HTML.SafeObject + using %Output.FlashCompat. +! Handle <ol><ol> properly, by inserting the necessary <li> tag. +- Always quote the insides of url(...) in CSS. + +4.0.0, released 2009-07-07 +# APIs for ConfigSchema subsystem have substantially changed. See + docs/dev-config-bcbreaks.txt for details; in essence, anything that + had both namespace and directive now have a single unified key. +# Some configuration directives were renamed, specifically: + %AutoFormatParam.PurifierLinkifyDocURL -> %AutoFormat.PurifierLinkify.DocURL + %FilterParam.ExtractStyleBlocksEscaping -> %Filter.ExtractStyleBlocks.Escaping + %FilterParam.ExtractStyleBlocksScope -> %Filter.ExtractStyleBlocks.Scope + %FilterParam.ExtractStyleBlocksTidyImpl -> %Filter.ExtractStyleBlocks.TidyImpl + As usual, the old directive names will still work, but will throw E_NOTICE + errors. +# The allowed values for class have been relaxed to allow all of CDATA for + doctypes that are not XHTML 1.1 or XHTML 2.0. For old behavior, set + %Attr.ClassUseCDATA to false. +# Instead of appending the content model to an old content model, a blank + element will replace the old content model. You can use #SUPER to get + the old content model. +! More robust support for name="" and id="" +! HTMLPurifier_Config::inherit($config) allows you to inherit one + configuration, and have changes to that configuration be propagated + to all of its children. +! Implement %HTML.Attr.Name.UseCDATA, which relaxes validation rules on + the name attribute when set. Use with care. Thanks Ian Cook for + sponsoring. +! Implement %AutoFormat.RemoveEmpty.RemoveNbsp, which removes empty + tags that contain non-breaking spaces as well other whitespace. You + can also modify which tags should have maintained with + %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions. +! Implement %Attr.AllowedClasses, which allows administrators to restrict + classes users can use to a specified finite set of classes, and + %Attr.ForbiddenClasses, which is the logical inverse. +! You can now maintain your own configuration schema directories by + creating a config-schema.php file or passing an extra argument. Check + docs/dev-config-schema.html for more details. +! Added HTMLPurifier_Config->serialize() method, which lets you save away + your configuration in a compact serial file, which you can unserialize + and use directly without having to go through the overhead of setup. +- Fix bug where URIDefinition would not get cleared if it's directives got + changed. +- Fix fatal error in HTMLPurifier_Encoder on certain platforms (probably NetBSD 5.0) +- Fix bug in Linkify autoformatter involving <a><span>http://foo</span></a> +- Make %URI.Munge not apply to links that have the same host as your host. +- Prevent stray </body> tag from truncating output, if a second </body> + is present. +. Created script maintenance/rename-config.php for renaming a configuration + directive while maintaining its alias. This script does not change source code. +. Implement namespace locking for definition construction, to prevent + bugs where a directive is used for definition construction but is not + used to construct the cache hash. + +3.3.0, released 2009-02-16 +! Implement CSS property 'overflow' when %CSS.AllowTricky is true. +! Implement generic property list classess +- Fix bug with testEncodingSupportsASCII() algorithm when iconv() implementation + does not do the "right thing" with characters not supported in the output + set. +- Spellcheck UTF-8: The Secret To Character Encoding +- Fix improper removal of the contents of elements with only whitespace. Thanks + Eric Wald for reporting. +- Fix broken test suite in versions of PHP without spl_autoload_register() +- Fix degenerate case with YouTube filter involving double hyphens. + Thanks Pierre Attar for reporting. +- Fix YouTube rendering problem on certain versions of Firefox. +- Fix CSSDefinition Printer problems with decorators +- Add text parameter to unit tests, forces text output +. Add verbose mode to command line test runner, use (--verbose) +. Turn on unit tests for UnitConverter +. Fix missing version number in configuration %Attr.DefaultImageAlt (added 3.2.0) +. Fix newline errors that caused spurious failures when CRLF HTML Purifier was + tested on Linux. +. Removed trailing whitespace from all text files, see + remote-trailing-whitespace.php maintenance script. +. Convert configuration to use property list backend. + +3.2.0, released 2008-10-31 +# Using %Core.CollectErrors forces line number/column tracking on, whereas + previously you could theoretically turn it off. +# HTMLPurifier_Injector->notifyEnd() is formally deprecated. Please + use handleEnd() instead. +! %Output.AttrSort for when you need your attributes in alphabetical order to + deal with a bug in FCKEditor. Requested by frank farmer. +! Enable HTML comments when %HTML.Trusted is on. Requested by Waldo Jaquith. +! Proper support for name attribute. It is now allowed and equivalent to the id + attribute in a and img tags, and is only converted to id when %HTML.TidyLevel + is heavy (for all doctypes). +! %AutoFormat.RemoveEmpty to remove some empty tags from documents. Please don't + use on hand-written HTML. +! Add error-cases for unsupported elements in MakeWellFormed. This enables + the strategy to be used, standalone, on untrusted input. +! %Core.AggressivelyFixLt is on by default. This causes more sensible + processing of left angled brackets in smileys and other whatnot. +! Test scripts now have a 'type' parameter, which lets you say 'htmlpurifier', + 'phpt', 'vtest', etc. in order to only execute those tests. This supercedes + the --only-phpt parameter, although for backwards-compatibility the flag + will still work. +! AutoParagraph auto-formatter will now preserve double-newlines upon output. + Users who are not performing inbound filtering, this may seem a little + useless, but as a bonus, the test suite and handling of edge cases is also + improved. +! Experimental implementation of forms for %HTML.Trusted +! Track column numbers when maintain line numbers is on +! Proprietary 'background' attribute on table-related elements converted into + corresponding CSS. Thanks Fusemail for sponsoring this feature! +! Add forward(), forwardUntilEndToken(), backward() and current() to Injector + supertype. +! HTMLPurifier_Injector->handleEnd() permits modification to end tokens. The + time of operation varies slightly from notifyEnd() as *all* end tokens are + processed by the injector before they are subject to the well-formedness rules. +! %Attr.DefaultImageAlt allows overriding default behavior of setting alt to + basename of image when not present. +! %AutoFormat.DisplayLinkURI neuters <a> tags into plain text URLs. +- Fix two bugs in %URI.MakeAbsolute; one involving empty paths in base URLs, + the other involving an undefined $is_folder error. +- Throw error when %Core.Encoding is set to a spurious value. Previously, + this errored silently and returned false. +- Redirected stderr to stdout for flush error output. +- %URI.DisableExternal will now use the host in %URI.Base if %URI.Host is not + available. +- Do not re-munge URL if the output URL has the same host as the input URL. + Requested by Chris. +- Fix error in documentation regarding %Filter.ExtractStyleBlocks +- Prevent <![CDATA[<body></body>]]> from triggering %Core.ConvertDocumentToFragment +- Fix bug with inline elements in blockquotes conflicting with strict doctype +- Detect if HTML support is disabled for DOM by checking for loadHTML() method. +- Fix bug where dots and double-dots in absolute URLs without hostname were + not collapsed by URIFilter_MakeAbsolute. +- Fix bug with anonymous modules operating on SafeEmbed or SafeObject elements + by reordering their addition. +- Will now throw exception on many error conditions during lexer creation; also + throw an exception when MaintainLineNumbers is true, but a non-tracksLineNumbers + is being used. +- Detect if domxml extension is loaded, and use DirectLEx accordingly. +- Improve handling of big numbers with floating point arithmetic in UnitConverter. + Reported by David Morton. +. Strategy_MakeWellFormed now operates in-place, saving memory and allowing + for more interesting filter-backtracking +. New HTMLPurifier_Injector->rewind() functionality, allows injectors to rewind + index to reprocess tokens. +. StringHashParser now allows for multiline sections with "empty" content; + previously the section would remain undefined. +. Added --quick option to multitest.php, which tests only the most recent + release for each series. +. Added --distro option to multitest.php, which accepts either 'normal' or + 'standalone'. This supercedes --exclude-normal and --exclude-standalone + +3.1.1, released 2008-06-19 +# %URI.Munge now, by default, does not munge resources (for example, <img src="">) + In order to enable this again, please set %URI.MungeResources to true. +! More robust imagecrash protection with height/width CSS with %CSS.MaxImgLength, + and height/width HTML with %HTML.MaxImgLength. +! %URI.MungeSecretKey for secure URI munging. Thanks Chris + for sponsoring this feature. Check out the corresponding documentation + for details. (Att Nightly testers: The API for this feature changed before + the general release. Namely, rename your directives %URI.SecureMungeSecretKey => + %URI.MungeSecretKey and and %URI.SecureMunge => %URI.Munge) +! Implemented post URI filtering. Set member variable $post to true to set + a URIFilter as such. +! Allow modules to define injectors via $info_injector. Injectors are + automatically disabled if injector's needed elements are not found. +! Support for "safe" objects added, use %HTML.SafeObject and %HTML.SafeEmbed. + Thanks Chris for sponsoring. If you've been using ad hoc code from the + forums, PLEASE use this instead. +! Added substitutions for %e, %n, %a and %p in %URI.Munge (in order, + embedded, tag name, attribute name, CSS property name). See %URI.Munge + for more details. Requested by Jochem Blok. +- Disable percent height/width attributes for img. +- AttrValidator operations are now atomic; updates to attributes are not + manifest in token until end of operations. This prevents naughty internal + code from directly modifying CurrentToken when they're not supposed to. + This semantics change was requested by frank farmer. +- Percent encoding checks enabled for URI query and fragment +- Fix stray backslashes in font-family; CSS Unicode character escapes are + now properly resolved (although *only* in font-family). Thanks Takeshi Terada + for reporting. +- Improve parseCDATA algorithm to take into account newline normalization +- Account for browser confusion between Yen character and backslash in + Shift_JIS encoding. This fix generalizes to any other encoding which is not + a strict superset of printable ASCII. Thanks Takeshi Terada for reporting. +- Fix missing configuration parameter in Generator calls. Thanks vs for the + partial patch. +- Improved adherence to Unicode by checking for non-character codepoints. + Thanks Geoffrey Sneddon for reporting. This may result in degraded + performance for extremely large inputs. +- Allow CSS property-value pair ''text-decoration: none''. Thanks Jochem Blok + for reporting. +. Added HTMLPurifier_UnitConverter and HTMLPurifier_Length for convenient + handling of CSS-style lengths. HTMLPurifier_AttrDef_CSS_Length now uses + this class. +. API of HTMLPurifier_AttrDef_CSS_Length changed from __construct($disable_negative) + to __construct($min, $max). __construct(true) is equivalent to + __construct('0'). +. Added HTMLPurifier_AttrDef_Switch class +. Rename HTMLPurifier_HTMLModule_Tidy->construct() to setup() and bubble method + up inheritance hierarchy to HTMLPurifier_HTMLModule. All HTMLModules + get this called with the configuration object. All modules now + use this rather than __construct(), although legacy code using constructors + will still work--the new format, however, lets modules access the + configuration object for HTML namespace dependant tweaks. +. AttrDef_HTML_Pixels now takes a single construction parameter, pixels. +. ConfigSchema data-structure heavily optimized; on average it uses a third + the memory it did previously. The interface has changed accordingly, + consult changes to HTMLPurifier_Config for details. +. Variable parsing types now are magic integers instead of strings +. Added benchmark for ConfigSchema +. HTMLPurifier_Generator requires $config and $context parameters. If you + don't know what they should be, use HTMLPurifier_Config::createDefault() + and new HTMLPurifier_Context(). +. Printers now properly distinguish between output configuration, and + target configuration. This is not applicable to scripts using + the Printers for HTML Purifier related tasks. +. HTML/CSS Printers must be primed with prepareGenerator($gen_config), otherwise + fatal errors will ensue. +. URIFilter->prepare can return false in order to abort loading of the filter +. Factory for AttrDef_URI implemented, URI#embedded to indicate URI that embeds + an external resource. +. %URI.Munge functionality factored out into a post-filter class. +. Added CurrentCSSProperty context variable during CSS validation + +3.1.0, released 2008-05-18 +# Unnecessary references to objects (vestiges of PHP4) removed from method + signatures. The following methods do not need references when assigning from + them and will result in E_STRICT errors if you try: + + HTMLPurifier_Config->get*Definition() [* = HTML, CSS] + + HTMLPurifier_ConfigSchema::instance() + + HTMLPurifier_DefinitionCacheFactory::instance() + + HTMLPurifier_DefinitionCacheFactory->create() + + HTMLPurifier_DoctypeRegistry->register() + + HTMLPurifier_DoctypeRegistry->get() + + HTMLPurifier_HTMLModule->addElement() + + HTMLPurifier_HTMLModule->addBlankElement() + + HTMLPurifier_LanguageFactory::instance() +# Printer_ConfigForm's get*() functions were static-ified +# %HTML.ForbiddenAttributes requires attribute declarations to be in the + form of tag@attr, NOT tag.attr (which will throw an error and won't do + anything). This is for forwards compatibility with XML; you'd do best + to migrate an %HTML.AllowedAttributes directives to this syntax too. +! Allow index to be false for config from form creation +! Added HTMLPurifier::VERSION constant +! Commas, not dashes, used for serializer IDs. This change is forwards-compatible + and allows for version numbers like "3.1.0-dev". +! %HTML.Allowed deals gracefully with whitespace anywhere, anytime! +! HTML Purifier's URI handling is a lot more robust, with much stricter + validation checks and better percent encoding handling. Thanks Gareth Heyes + for indicating security vulnerabilities from lax percent encoding. +! Bootstrap autoloader deals more robustly with classes that don't exist, + preventing class_exists($class, true) from barfing. +- InterchangeBuilder now alphabetizes its lists +- Validation error in configdoc output fixed +- Iconv and other encoding errors muted even with custom error handlers that + do not honor error_reporting +- Add protection against imagecrash attack with CSS height/width +- HTMLPurifier::instance() created for consistency, is equivalent to getInstance() +- Fixed and revamped broken ConfigForm smoketest +- Bug with bool/null fields in Printer_ConfigForm fixed +- Bug with global forbidden attributes fixed +- Improved error messages for allowed and forbidden HTML elements and attributes +- Missing (or null) in configdoc documentation restored +- If DOM throws and exception during parsing with PH5P (occurs in newer versions + of DOM), HTML Purifier punts to DirectLex +- Fatal error with unserialization of ScriptRequired +- Created directories are now chmod'ed properly +- Fixed bug with fallback languages in LanguageFactory +- Standalone testing setup properly with autoload +. Out-of-date documentation revised +. UTF-8 encoding check optimization as suggested by Diego +. HTMLPurifier_Error removed in favor of exceptions +. More copy() function removed; should use clone instead +. More extensive unit tests for HTMLDefinition +. assertPurification moved to central harness +. HTMLPurifier_Generator accepts $config and $context parameters during + instantiation, not runtime +. Double-quotes outside of attribute values are now unescaped + +3.1.0rc1, released 2008-04-22 +# Autoload support added. Internal require_once's removed in favor of an + explicit require list or autoloading. To use HTML Purifier, + you must now either use HTMLPurifier.auto.php + or HTMLPurifier.includes.php; setting the include path and including + HTMLPurifier.php is insufficient--in such cases include HTMLPurifier.autoload.php + as well to register our autoload handler (or modify your autoload function + to check HTMLPurifier_Bootstrap::getPath($class)). You can also use + HTMLPurifier.safe-includes.php for a less performance friendly but more + user-friendly library load. +# HTMLPurifier_ConfigSchema static functions are officially deprecated. Schema + information is stored in the ConfigSchema directory, and the + maintenance/generate-schema-cache.php generates the schema.ser file, which + is now instantiated. Support for userland schema changes coming soon! +# HTMLPurifier_Config will now throw E_USER_NOTICE when you use a directive + alias; to get rid of these errors just modify your configuration to use + the new directive name. +# HTMLPurifier->addFilter is deprecated; built-in filters can now be + enabled using %Filter.$filter_name or by setting your own filters using + %Filter.Custom +# Directive-level safety properties superceded in favor of module-level + safety. Internal method HTMLModule->addElement() has changed, although + the externally visible HTMLDefinition->addElement has *not* changed. +! Extra utility classes for testing and non-library operations can + be found in extras/. Specifically, these are FSTools and ConfigDoc. + You may find a use for these in your own project, but right now they + are highly experimental and volatile. +! Integration with PHPT allows for automated smoketests +! Limited support for proprietary HTML elements, namely <marquee>, sponsored + by Chris. You can enable them with %HTML.Proprietary if your client + demands them. +! Support for !important CSS cascade modifier. By default, this will be stripped + from CSS, but you can enable it using %CSS.AllowImportant +! Support for display and visibility CSS properties added, set %CSS.AllowTricky + to true to use them. +! HTML Purifier now has its own Exception hierarchy under HTMLPurifier_Exception. + Developer error (not enduser error) can cause these to be triggered. +! Experimental kses() wrapper introduced with HTMLPurifier.kses.php +! Finally %CSS.AllowedProperties for tweaking allowed CSS properties without + mucking around with HTMLPurifier_CSSDefinition +! ConfigDoc output has been enhanced with version and deprecation info. +! %HTML.ForbiddenAttributes and %HTML.ForbiddenElements implemented. +- Autoclose now operates iteratively, i.e. <span><span><div> now has + both span tags closed. +- Various HTMLPurifier_Config convenience functions now accept another parameter + $schema which defines what HTMLPurifier_ConfigSchema to use besides the + global default. +- Fix bug with trusted script handling in libxml versions later than 2.6.28. +- Fix bug in ExtractStyleBlocks with comments in style tags +- Fix bug in comment parsing for DirectLex +- Flush output now displayed when in command line mode for unit tester +- Fix bug with rgb(0, 1, 2) color syntax with spaces inside shorthand syntax +- HTMLPurifier_HTMLDefinition->addAttribute can now be called multiple times + on the same element without emitting errors. +- Fixed fatal error in PH5P lexer with invalid tag names +. Plugins now get their own changelogs according to project conventions. +. Convert tokens to use instanceof, reducing memory footprint and + improving comparison speed. +. Dry runs now supported in SimpleTest; testing facilities improved +. Bootstrap class added for handling autoloading functionality +. Implemented recursive glob at FSTools->globr +. ConfigSchema now has instance methods for all corresponding define* + static methods. +. A couple of new historical maintenance scripts were added. +. HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php split into two files +. tests/index.php can now be run from any directory. +. HTMLPurifier_Token subclasses split into seperate files +. HTMLPURIFIER_PREFIX now is defined in Bootstrap.php, NOT HTMLPurifier.php +. HTMLPURIFIER_PREFIX can now be defined outside of HTML Purifier +. New --php=php flag added, allows PHP executable to be specified (command + line only!) +. htmlpurifier_add_test() preferred method to translate test files in to + classes, because it handles PHPT files too. +. Debugger class is deprecated and will be removed soon. +. Command line argument parsing for testing scripts revamped, now --opt value + format is supported. +. Smoketests now cleanup after magic quotes +. Generator now can output comments (however, comments are still stripped + from HTML Purifier output) +. HTMLPurifier_ConfigSchema->validate() deprecated in favor of + HTMLPurifier_VarParser->parse() +. Integers auto-cast into float type by VarParser. +. HTMLPURIFIER_STRICT removed; no validation is performed on runtime, only + during cache generation +. Reordered script calls in maintenance/flush.php +. Command line scripts now honor exit codes +. When --flush fails in unit testers, abort tests and print message +. Improved documentation in docs/dev-flush.html about the maintenance scripts +. copy() methods removed in favor of clone keyword + +3.0.0, released 2008-01-06 +# HTML Purifier is PHP 5 only! The 2.1.x branch will be maintained + until PHP 4 is completely deprecated, but no new features will be added + to it. + + Visibility declarations added + + Constructor methods renamed to __construct() + + PHP4 reference cruft removed (in progress) +! CSS properties are now case-insensitive +! DefinitionCacheFactory now can register new implementations +! New HTMLPurifier_Filter_ExtractStyleBlocks for extracting <style> from + documents and cleaning their contents up. Requires the CSSTidy library + <http://csstidy.sourceforge.net/>. You can access the blocks with the + 'StyleBlocks' Context variable ($purifier->context->get('StyleBlocks')). + The output CSS can also be "scoped" for a specific element, use: + %Filter.ExtractStyleBlocksScope +! Experimental support for some proprietary CSS attributes allowed: + opacity (and all of the browser-specific equivalents) and scrollbar colors. + Enable by setting %CSS.Proprietary to true. +- Colors missing # but in hex form will be corrected +- CSS Number algorithm improved +- Unit testing and multi-testing now on steroids: command lines, + XML output, and other goodies now added. +. Unit tests for Injector improved +. New classes: + + HTMLPurifier_AttrDef_CSS_AlphaValue + + HTMLPurifier_AttrDef_CSS_Filter +. Multitest now has a file docblock + +2.1.3, released 2007-11-05 +! tests/multitest.php allows you to test multiple versions by running + tests/index.php through multiple interpreters using `phpv` shell + script (you must provide this script!) +- Fixed poor include ordering for Email URI AttrDefs, causes fatal errors + on some systems. +- Injector algorithm further refined: off-by-one error regarding skip + counts for dormant injectors fixed +- Corrective blockquote definition now enabled for HTML 4.01 Strict +- Fatal error when <img> tag (or any other element with required attributes) + has 'id' attribute fixed, thanks NykO18 for reporting +- Fix warning emitted when a non-supported URI scheme is passed to the + MakeAbsolute URIFilter, thanks NykO18 (again) +- Further refine AutoParagraph injector. Behavior inside of elements + allowing paragraph tags clarified: only inline content delimeted by + double newlines (not block elements) are paragraphed. +- Buggy treatment of end tags of elements that have required attributes + fixed (does not manifest on default tag-set) +- Spurious internal content reorganization error suppressed +- HTMLDefinition->addElement now returns a reference to the created + element object, as implied by the documentation +- Phorum mod's HTML Purifier help message expanded (unreleased elsewhere) +- Fix a theoretical class of infinite loops from DirectLex reported + by Nate Abele +- Work around unnecessary DOMElement type-cast in PH5P that caused errors + in PHP 5.1 +- Work around PHP 4 SimpleTest lack-of-error complaining for one-time-only + HTMLDefinition errors, this may indicate problems with error-collecting + facilities in PHP 5 +- Make ErrorCollectorEMock work in both PHP 4 and PHP 5 +- Make PH5P work with PHP 5.0 by removing unnecessary array parameter typedef +. %Core.AcceptFullDocuments renamed to %Core.ConvertDocumentToFragment + to better communicate its purpose +. Error unit tests can now specify the expectation of no errors. Future + iterations of the harness will be extremely strict about what errors + are allowed +. Extend Injector hooks to allow for more powerful injector routines +. HTMLDefinition->addBlankElement created, as according to the HTMLModule + method +. Doxygen configuration file updated, with minor improvements +. Test runner now checks for similarly named files in conf/ directory too. +. Minor cosmetic change to flush-definition-cache.php: trailing newline is + outputted +. Maintenance script for generating PH5P patch added, original PH5P source + file also added under version control +. Full unit test runner script title made more descriptive with PHP version +. Updated INSTALL file to state that 4.3.7 is the earliest version we + are actively testing + +2.1.2, released 2007-09-03 +! Implemented Object module for trusted users +! Implemented experimental HTML5 parsing mode using PH5P. To use, add + this to your code: + require_once 'HTMLPurifier/Lexer/PH5P.php'; + $config->set('Core', 'LexerImpl', 'PH5P'); + Note that this Lexer introduces some classes not in the HTMLPurifier + namespace. Also, this is PHP5 only. +! CSS property border-spacing implemented +- Fix non-visible parsing error in DirectLex with empty tags that have + slashes inside attribute values. +- Fix typo in CSS definition: border-collapse:seperate; was incorrectly + accepted as valid CSS. Usually non-visible, because this styling is the + default for tables in most browsers. Thanks Brett Zamir for pointing + this out. +- Fix validation errors in configuration form +- Hammer out a bunch of edge-case bugs in the standalone distribution +- Inclusion reflection removed from URISchemeRegistry; you must manually + include any new schema files you wish to use +- Numerous typo fixes in documentation thanks to Brett Zamir +. Unit test refactoring for one logical test per test function +. Config and context parameters in ComplexHarness deprecated: instead, edit + the $config and $context member variables +. HTML wrapper in DOMLex now takes DTD identifiers into account; doesn't + really make a difference, but is good for completeness sake +. merge-library.php script refactored for greater code reusability and + PHP4 compatibility + +2.1.1, released 2007-08-04 +- Fix show-stopper bug in %URI.MakeAbsolute functionality +- Fix PHP4 syntax error in standalone version +. Add prefix directory to include path for standalone, this prevents + other installations from clobbering the standalone's URI schemes +. Single test methods can be invoked by prefixing with __only + +2.1.0, released 2007-08-02 +# flush-htmldefinition-cache.php superseded in favor of a generic + flush-definition-cache.php script, you can clear a specific cache + by passing its name as a parameter to the script +! Phorum mod implemented for HTML Purifier +! With %Core.AggressivelyFixLt, <3 and similar emoticons no longer + trigger HTML removal in PHP5 (DOMLex). This directive is not necessary + for PHP4 (DirectLex). +! Standalone file now available, which greatly reduces the amount of + includes (although there are still a few files that reside in the + standalone folder) +! Relative URIs can now be transformed into their absolute equivalents + using %URI.Base and %URI.MakeAbsolute +! Ruby implemented for XHTML 1.1 +! You can now define custom URI filtering behavior, see enduser-uri-filter.html + for more details +! UTF-8 font names now supported in CSS +- AutoFormatters emit friendly error messages if tags or attributes they + need are not allowed +- ConfigForm's compactification of directive names is now configurable +- AutoParagraph autoformatter algorithm refined after field-testing +- XHTML 1.1 now applies XHTML 1.0 Strict cleanup routines, namely + blockquote wrapping +- Contents of <style> tags removed by default when tags are removed +. HTMLPurifier_Config->getSerial() implemented, this is extremely useful + for output cache invalidation +. ConfigForm printer now can retrieve CSS and JS files as strings, in + case HTML Purifier's directory is not publically accessible +. Introduce new text/itext configuration directive values: these represent + longer strings that would be more appropriately edited with a textarea +. Allow newlines to act as separators for lists, hashes, lookups and + %HTML.Allowed +. ConfigForm generates textareas instead of text inputs for lists, hashes, + lookups, text and itext fields +. Hidden element content removal genericized: %Core.HiddenElements can + be used to customize this behavior, by default <script> and <style> are + hidden +. Added HTMLPURIFIER_PREFIX constant, should be used instead of dirname(__FILE__) +. Custom ChildDef added to default include list +. URIScheme reflection improved: will not attempt to include file if class + already exists. May clobber autoload, so I need to keep an eye on it +. ConfigSchema heavily optimized, will only collect information and validate + definitions when HTMLPURIFIER_SCHEMA_STRICT is true. +. AttrDef_URI unit tests and implementation refactored +. benchmarks/ directory now protected from public view with .htaccess file; + run the tests via command line +. URI scheme is munged off if there is no authority and the scheme is the + default one +. All unit tests inherit from HTMLPurifier_Harness, not UnitTestCase +. Interface for URIScheme changed +. Generic URI object to hold components of URI added, most systems involved + in URI validation have been migrated to use it +. Custom filtering for URIs factored out to URIDefinition interface for + maximum extensibility + +2.0.1, released 2007-06-27 +! Tag auto-closing now based on a ChildDef heuristic rather than a + manually set auto_close array; some behavior may change +! Experimental AutoFormat functionality added: auto-paragraph and + linkify your HTML input by setting %AutoFormat.AutoParagraph and + %AutoFormat.Linkify to true +! Newlines normalized internally, and then converted back to the + value of PHP_EOL. If this is not desired, set your newline format + using %Output.Newline. +! Beta error collection, messages are implemented for the most generic + cases involving Lexing or Strategies +- Clean up special case code for <script> tags +- Reorder includes for DefinitionCache decorators, fixes a possible + missing class error +- Fixed bug where manually modified definitions were not saved via cache + (mostly harmless, except for the fact that it would be a little slower) +- Configuration objects with different serials do not clobber each + others when revision numbers are unequal +- Improve Serializer DefinitionCache directory permissions checks +- DefinitionCache no longer throws errors when it encounters old + serial files that do not conform to the current style +- Stray xmlns attributes removed from configuration documentation +- configForm.php smoketest no longer has XSS vulnerability due to + unescaped print_r output +- Printer adheres to configuration's directives on output format +- Fix improperly named form field in ConfigForm printer +. Rewire some test-cases to swallow errors rather than expect them +. HTMLDefinition printer updated with some of the new attributes +. DefinitionCache keys reordered to reflect precedence: version number, + hash, then revision number +. %Core.DefinitionCache renamed to %Cache.DefinitionImpl +. Interlinking in configuration documentation added using + Injector_PurifierLinkify +. Directives now keep track of aliases to themselves +. Error collector now requires a severity to be passed, use PHP's internal + error constants for this +. HTMLPurifier_Config::getAllowedDirectivesForForm implemented, allows + much easier selective embedding of configuration values +. Doctype objects now accept public and system DTD identifiers +. %HTML.Doctype is now constrained by specific values, to specify a custom + doctype use new %HTML.CustomDoctype +. ConfigForm truncates long directives to keep the form small, and does + not re-output namespaces + +2.0.0, released 2007-06-20 +# Completely refactored HTMLModuleManager, decentralizing safety + information +# Transform modules changed to Tidy modules, which offer more flexibility + and better modularization +# Configuration object now finalizes itself when a read operation is + performed on it, ensuring that its internal state stays consistent. + To revert this behavior, you can set the $autoFinalize member variable + off, but it's not recommended. +# New compact syntax for AttrDef objects that can be used to instantiate + new objects via make() +# Definitions (esp. HTMLDefinition) are now cached for a significant + performance boost. You can disable caching by setting %Core.DefinitionCache + to null. You CANNOT edit raw definitions without setting the corresponding + DefinitionID directive (%HTML.DefinitionID for HTMLDefinition). +# Contents between <script> tags are now completely removed if <script> + is not allowed +# Prototype-declarations for Lexer removed in favor of configuration + determination of Lexer implementations. +! HTML Purifier now works in PHP 4.3.2. +! Configuration form-editing API makes tweaking HTMLPurifier_Config a + breeze! +! Configuration directives that accept hashes now allow new string + format: key1:value1,key2:value2 +! ConfigDoc now factored into OOP design +! All deprecated elements now natively supported +! Implement TinyMCE styled whitelist specification format in + %HTML.Allowed +! Config object gives more friendly error messages when things go wrong +! Advanced API implemented: easy functions for creating elements (addElement) + and attributes (addAttribute) on HTMLDefinition +! Add native support for required attributes +- Deprecated and removed EnableRedundantUTF8Cleaning. It didn't even work! +- DOMLex will not emit errors when a custom error handler that does not + honor error_reporting is used +- StrictBlockquote child definition refrains from wrapping whitespace + in tags now. +- Bug resulting from tag transforms to non-allowed elements fixed +- ChildDef_Custom's regex generation has been improved, removing several + false positives +. Unit test for ElementDef created, ElementDef behavior modified to + be more flexible +. Added convenience functions for HTMLModule constructors +. AttrTypes now has accessor functions that should be used instead + of directly manipulating info +. TagTransform_Center deprecated in favor of generic TagTransform_Simple +. Add extra protection in AttrDef_URI against phantom Schemes +. Doctype object added to HTMLDefinition which describes certain aspects + of the operational document type +. Lexer is now pre-emptively included, with a conditional include for the + PHP5 only version. +. HTMLDefinition and CSSDefinition have a common parent class: Definition. +. DirectLex can now track line-numbers +. Preliminary error collector is in place, although no code actually reports + errors yet +. Factor out most of ValidateAttributes to new AttrValidator class + +1.6.1, released 2007-05-05 +! Support for more deprecated attributes via transformations: + + hspace and vspace in img + + size and noshade in hr + + nowrap in td + + clear in br + + align in caption, table, img and hr + + type in ul, ol and li +! DirectLex now preserves text in which a < bracket is followed by + a non-alphanumeric character. This means that certain emoticons + are now preserved. +! %Core.RemoveInvalidImg is now operational, when set to false invalid + images will hang around with an empty src +! target attribute in a tag supported, use %Attr.AllowedFrameTargets + to enable +! CSS property white-space now allows nowrap (supported in all modern + browsers) but not others (which have spotty browser implementations) +! XHTML 1.1 mode now sort-of works without any fatal errors, and + lang is now moved over to xml:lang. +! Attribute transformation smoketest available at smoketests/attrTransform.php +! Transformation of font's size attribute now handles super-large numbers +- Possibly fatal bug with __autoload() fixed in module manager +- Invert HTMLModuleManager->addModule() processing order to check + prefixes first and then the literal module +- Empty strings get converted to empty arrays instead of arrays with + an empty string in them. +- Merging in attribute lists now works. +. Demo script removed: it has been added to the website's repository +. Basic.php script modified to work out of the box +. Refactor AttrTransform classes to reduce duplication +. AttrTransform_TextAlign axed in favor of a more general + AttrTransform_EnumToCSS, refer to HTMLModule/TransformToStrict.php to + see how the new equivalent is implemented +. Unit tests now use exclusively assertIdentical + +1.6.0, released 2007-04-01 +! Support for most common deprecated attributes via transformations: + + bgcolor in td, th, tr and table + + border in img + + name in a and img + + width in td, th and hr + + height in td, th +! Support for CSS attribute 'height' added +! Support for rel and rev attributes in a tags added, use %Attr.AllowedRel + and %Attr.AllowedRev to activate +- You can define ID blacklists using regular expressions via + %Attr.IDBlacklistRegexp +- Error messages are emitted when you attempt to "allow" elements or + attributes that HTML Purifier does not support +- Fix segfault in unit test. The problem is not very reproduceable and + I don't know what causes it, but a six line patch fixed it. + +1.5.0, released 2007-03-23 +! Added a rudimentary I18N and L10N system modeled off MediaWiki. It + doesn't actually do anything yet, but keep your eyes peeled. +! docs/enduser-utf8.html explains how to use UTF-8 and HTML Purifier +! Newly structured HTMLDefinition modeled off of XHTML 1.1 modules. + I am loathe to release beta quality APIs, but this is exactly that; + don't use the internal interfaces if you're not willing to do migration + later on. +- Allow 'x' subtag in language codes +- Fixed buggy chameleon-support for ins and del +. Added support for IDREF attributes (i.e. for) +. Renamed HTMLPurifier_AttrDef_Class to HTMLPurifier_AttrDef_Nmtokens +. Removed context variable ParentType, replaced with IsInline, which + is false when you're not inline and an integer of the parent that + caused you to become inline when you are (so possibly zero) +. Removed ElementDef->type in favor of ElementDef->descendants_are_inline + and HTMLDefinition->content_sets +. StrictBlockquote now reports what elements its supposed to allow, + rather than what it does allow +. Removed HTMLDefinition->info_flow_elements in favor of + HTMLDefinition->content_sets['Flow'] +. Removed redundant "exclusionary" definitions from DTD roster +. StrictBlockquote now requires a construction parameter as if it + were an Required ChildDef, this is the "real" set of allowed elements +. AttrDef partitioned into HTML, CSS and URI segments +. Modify Youtube filter regexp to be multiline +. Require both PHP5 and DOM extension in order to use DOMLex, fixes + some edge cases where a DOMDocument class exists in a PHP4 environment + due to DOM XML extension. + +1.4.1, released 2007-01-21 +! docs/enduser-youtube.html updated according to new functionality +- YouTube IDs can have underscores and dashes + +1.4.0, released 2007-01-21 +! Implemented list-style-image, URIs now allowed in list-style +! Implemented background-image, background-repeat, background-attachment + and background-position CSS properties. Shorthand property background + supports all of these properties. +! Configuration documentation looks nicer +! Added %Core.EscapeNonASCIICharacters to workaround loss of Unicode + characters while %Core.Encoding is set to a non-UTF-8 encoding. +! Support for configuration directive aliases added +! Config object can now be instantiated from ini files +! YouTube preservation code added to the core, with two lines of code + you can add it as a filter to your code. See smoketests/preserveYouTube.php + for sample code. +! Moved SLOW to docs/enduser-slow.html and added code examples +- Replaced version check with functionality check for DOM (thanks Stephen + Khoo) +. Added smoketest 'all.php', which loads all other smoketests via frames +. Implemented AttrDef_CSSURI for url(http://google.com) style declarations +. Added convenient single test selector form on test runner + +1.3.2, released 2006-12-25 +! HTMLPurifier object now accepts configuration arrays, no need to manually + instantiate a configuration object +! Context object now accessible to outside +! Added enduser-youtube.html, explains how to embed YouTube videos. See + also corresponding smoketest preserveYouTube.php. +! Added purifyArray(), which takes a list of HTML and purifies it all +! Added static member variable $version to HTML Purifier with PHP-compatible + version number string. +- Fixed fatal error thrown by upper-cased language attributes +- printDefinition.php: added labels, added better clarification +. HTMLPurifier_Config::create() added, takes mixed variable and converts into + a HTMLPurifier_Config object. + +1.3.1, released 2006-12-06 +! Added HTMLPurifier.func.php stub for a convenient function to call the library +- Fixed bug in RemoveInvalidImg code that caused all images to be dropped + (thanks to .mario for reporting this) +. Standardized all attribute handling variables to attr, made it plural + +1.3.0, released 2006-11-26 +# Invalid images are now removed, rather than replaced with a dud + <img src="" alt="Invalid image" />. Previous behavior can be restored + with new directive %Core.RemoveInvalidImg set to false. +! (X)HTML Strict now supported + + Transparently handles inline elements in block context (blockquote) +! Added GET method to demo for easier validation, added 50kb max input size +! New directive %HTML.BlockWrapper, for block-ifying inline elements +! New directive %HTML.Parent, allows you to only allow inline content +! New directives %HTML.AllowedElements and %HTML.AllowedAttributes to let + users narrow the set of allowed tags +! <li value="4"> and <ul start="2"> now allowed in loose mode +! New directives %URI.DisableExternalResources and %URI.DisableResources +! New directive %Attr.DisableURI, which eliminates all hyperlinking +! New directive %URI.Munge, munges URI so you can use some sort of redirector + service to avoid PageRank leaks or warn users that they are exiting your site. +! Added spiffy new smoketest printDefinition.php, which lets you twiddle with + the configuration settings and see how the internal rules are affected. +! New directive %URI.HostBlacklist for blocking links to bad hosts. + xssAttacks.php smoketest updated accordingly. +- Added missing type to ChildDef_Chameleon +- Remove Tidy option from demo if there is not Tidy available +. ChildDef_Required guards against empty tags +. Lookup table HTMLDefinition->info_flow_elements added +. Added peace-of-mind variable initialization to Strategy_FixNesting +. Added HTMLPurifier->info_parent_def, parent child processing made special +. Added internal documents briefly summarizing future progression of HTML +. HTMLPurifier_Config->getBatch($namespace) added +. More lenient casting to bool from string in HTMLPurifier_ConfigSchema +. Refactored ChildDef classes into their own files + +1.2.0, released 2006-11-19 +# ID attributes now disabled by default. New directives: + + %HTML.EnableAttrID - restores old behavior by allowing IDs + + %Attr.IDPrefix - %Attr.IDBlacklist alternative that munges all user IDs + so that they don't collide with your IDs + + %Attr.IDPrefixLocal - Same as above, but for when there are multiple + instances of user content on the page + + Profuse documentation on how to use these available in docs/enduser-id.txt +! Added MODx plugin <http://modxcms.com/forums/index.php/topic,6604.0.html> +! Added percent encoding normalization +! XSS attacks smoketest given facelift +! Configuration documentation now has table of contents +! Added %URI.DisableExternal, which prevents links to external websites. You + can also use %URI.Host to permit absolute linking to subdomains +! Non-accessible resources (ex. mailto) blocked from embedded URIs (img src) +- Type variable in HTMLDefinition was not being set properly, fixed +- Documentation updated + + TODO added request Phalanger + + TODO added request Native compression + + TODO added request Remove redundant tags + + TODO added possible plaintext formatter for HTML Purifier documentation + + Updated ConfigDoc TODO + + Improved inline comments in AttrDef/Class.php, AttrDef/CSS.php + and AttrDef/Host.php + + Revamped documentation into HTML, along with misc updates +- HTMLPurifier_Context doesn't throw a variable reference error if you attempt + to retrieve a non-existent variable +. Switched to purify()-wide Context object registry +. Refactored unit tests to minimize duplication +. XSS attack sheet updated +. configdoc.xml now has xml:space attached to default value nodes +. Allow configuration directives to permit null values +. Cleaned up test-cases to remove unnecessary swallowErrors() + +1.1.2, released 2006-09-30 +! Add HTMLPurifier.auto.php stub file that configures include_path +- Documentation updated + + INSTALL document rewritten + + TODO added semi-lossy conversion + + API Doxygen docs' file exclusions updated + + Added notes on HTML versus XML attribute whitespace handling + + Noted that HTMLPurifier_ChildDef_Custom isn't being used + + Noted that config object's definitions are cached versions +- Fixed lack of attribute parsing in HTMLPurifier_Lexer_PEARSax3 +- ftp:// URIs now have their typecodes checked +- Hooked up HTMLPurifier_ChildDef_Custom's unit tests (they weren't being run) +. Line endings standardized throughout project (svn:eol-style standardized) +. Refactored parseData() to general Lexer class +. Tester named "HTML Purifier" not "HTMLPurifier" + +1.1.1, released 2006-09-24 +! Configuration option to optionally Tidy up output for indentation to make up + for dropped whitespace by DOMLex (pretty-printing for the entire application + should be done by a page-wide Tidy) +- Various documentation updates +- Fixed parse error in configuration documentation script +- Fixed fatal error in benchmark scripts, slightly augmented +- As far as possible, whitespace is preserved in-between table children +- Sample test-settings.php file included + +1.1.0, released 2006-09-16 +! Directive documentation generation using XSLT +! XHTML can now be turned off, output becomes <br> +- Made URI validator more forgiving: will ignore leading and trailing + quotes, apostrophes and less than or greater than signs. +- Enforce alphanumeric namespace and directive names for configuration. +- Table child definition made more flexible, will fix up poorly ordered elements +. Renamed ConfigDef to ConfigSchema + +1.0.1, released 2006-09-04 +- Fixed slight bug in DOMLex attribute parsing +- Fixed rejection of case-insensitive configuration values when there is a + set of allowed values. This manifested in %Core.Encoding. +- Fixed rejection of inline style declarations that had lots of extra + space in them. This manifested in TinyMCE. + +1.0.0, released 2006-09-01 +! Shorthand CSS properties implemented: font, border, background, list-style +! Basic color keywords translated into hexadecimal values +! Table CSS properties implemented +! Support for charsets other than UTF-8 (defined by iconv) +! Malformed UTF-8 and non-SGML character detection and cleaning implemented +- Fixed broken numeric entity conversion +- API documentation completed +. (HTML|CSS)Definition de-singleton-ized + +1.0.0beta, released 2006-08-16 +! First public release, most functionality implemented. Notable omissions are: + + Shorthand CSS properties + + Table CSS properties + + Deprecated attribute transformations + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/README b/library/ezyang/htmlpurifier/README new file mode 100644 index 0000000000..53f26f1c28 --- /dev/null +++ b/library/ezyang/htmlpurifier/README @@ -0,0 +1,24 @@ + +README + All about HTML Purifier + +HTML Purifier is an HTML filtering solution that uses a unique combination +of robust whitelists and agressive parsing to ensure that not only are +XSS attacks thwarted, but the resulting HTML is standards compliant. + +HTML Purifier is oriented towards richly formatted documents from +untrusted sources that require CSS and a full tag-set. This library can +be configured to accept a more restrictive set of tags, but it won't be +as efficient as more bare-bones parsers. It will, however, do the job +right, which may be more important. + +Places to go: + +* See INSTALL for a quick installation guide +* See docs/ for developer-oriented documentation, code examples and + an in-depth installation guide. +* See WYSIWYG for information on editors like TinyMCE and FCKeditor + +HTML Purifier can be found on the web at: http://htmlpurifier.org/ + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/TODO b/library/ezyang/htmlpurifier/TODO new file mode 100644 index 0000000000..a92abf2802 --- /dev/null +++ b/library/ezyang/htmlpurifier/TODO @@ -0,0 +1,150 @@ + +TODO List + += KEY ==================== + # Flagship + - Regular + ? Maybe I'll Do It +========================== + +If no interest is expressed for a feature that may require a considerable +amount of effort to implement, it may get endlessly delayed. Do not be +afraid to cast your vote for the next feature to be implemented! + +Things to do as soon as possible: + + - http://htmlpurifier.org/phorum/read.php?3,5560,6307#msg-6307 + - Think about allowing explicit order of operations hooks for transforms + - Fix "<.<" bug (trailing < is removed if not EOD) + - Build in better internal state dumps and debugging tools for remote + debugging + - Allowed/Allowed* have strange interactions when both set + ? Transform lone embeds into object tags + - Deprecated config options that emit warnings when you set them (with' + a way of muting the warning if you really want to) + - Make HTML.Trusted work with Output.FlashCompat + - HTML.Trusted and HTML.SafeObject have funny interaction; general + problem is what to do when a module "supersedes" another + (see also tables and basic tables.) This is a little dicier + because HTML.SafeObject has some extra functionality that + trusted might find useful. See http://htmlpurifier.org/phorum/read.php?3,5762,6100 + +FUTURE VERSIONS +--------------- + +4.8 release [OMG CONFIG PONIES] + ! Fix Printer. It's from the old days when we didn't have decent XML classes + ! Factor demo.php into a set of Printer classes, and then create a stub + file for users here (inside the actual HTML Purifier library) + - Fix error handling with form construction + - Do encoding validation in Printers, or at least, where user data comes in + - Config: Add examples to everything (make built-in which also automatically + gives output) + - Add "register" field to config schemas to eliminate dependence on + naming conventions (try to remember why we ultimately decided on tihs) + +5.0 release [HTML 5] + # Swap out code to use html5lib tokenizer and tree-builder + ! Allow turning off of FixNesting and required attribute insertion + +5.1 release [It's All About Trust] (floating) + # Implement untrusted, dangerous elements/attributes + # Implement IDREF support (harder than it seems, since you cannot have + IDREFs to non-existent IDs) + - Implement <area> (client and server side image maps are blocking + on IDREF support) + # Frameset XHTML 1.0 and HTML 4.01 doctypes + - Figure out how to simultaneously set %CSS.Trusted and %HTML.Trusted (?) + +5.2 release [Error'ed] + # Error logging for filtering/cleanup procedures + # Additional support for poorly written HTML + - Microsoft Word HTML cleaning (i.e. MsoNormal, but research essential!) + - Friendly strict handling of <address> (block -> <br>) + - XSS-attempt detection--certain errors are flagged XSS-like + - Append something to duplicate IDs so they're still usable (impl. note: the + dupe detector would also need to detect the suffix as well) + +6.0 release [Beyond HTML] + # Legit token based CSS parsing (will require revamping almost every + AttrDef class). Probably will use CSSTidy + # More control over allowed CSS properties using a modularization + # IRI support (this includes IDN) + - Standardize token armor for all areas of processing + +7.0 release [To XML and Beyond] + - Extended HTML capabilities based on namespacing and tag transforms (COMPLEX) + - Hooks for adding custom processors to custom namespaced tags and + attributes, offer default implementation + - Lots of documentation and samples + +Ongoing + - More refactoring to take advantage of PHP5's facilities + - Refactor unit tests into lots of test methods + - Plugins for major CMSes (COMPLEX) + - phpBB + - Also, a FAQ for extension writers with HTML Purifier + +AutoFormat + - Smileys + - Syntax highlighting (with GeSHi) with <pre> and possibly <?php + - Look at http://drupal.org/project/Modules/category/63 for ideas + +Neat feature related + ! Support exporting configuration, so users can easily tweak settings + in the demo, and then copy-paste into their own setup + - Advanced URI filtering schemes (see docs/proposal-new-directives.txt) + - Allow scoped="scoped" attribute in <style> tags; may be troublesome + because regular CSS has no way of uniquely identifying nodes, so we'd + have to generate IDs + - Explain how to use HTML Purifier in non-PHP languages / create + a simple command line stub (or complicated?) + - Fixes for Firefox's inability to handle COL alignment props (Bug 915) + - Automatically add non-breaking spaces to empty table cells when + empty-cells:show is applied to have compatibility with Internet Explorer + - Table of Contents generation (XHTML Compiler might be reusable). May also + be out-of-band information. + - Full set of color keywords. Also, a way to add onto them without + finalizing the configuration object. + - Write a var_export and memcached DefinitionCache - Denis + - Built-in support for target="_blank" on all external links + - Convert RTL/LTR override characters to <bdo> tags, or vice versa on demand. + Also, enable disabling of directionality + ? Externalize inline CSS to promote clean HTML, proposed by Sander Tekelenburg + ? Remove redundant tags, ex. <u><u>Underlined</u></u>. Implementation notes: + 1. Analyzing which tags to remove duplicants + 2. Ensure attributes are merged into the parent tag + 3. Extend the tag exclusion system to specify whether or not the + contents should be dropped or not (currently, there's code that could do + something like this if it didn't drop the inner text too.) + ? Make AutoParagraph also support paragraph-izing double <br> tags, and not + just double newlines. This is kind of tough to do in the current framework, + though, and might be reasonably approximated by search replacing double <br>s + with newlines before running it through HTML Purifier. + +Maintenance related (slightly boring) + # CHMOD install script for PEAR installs + ! Factor out command line parser into its own class, and unit test it + - Reduce size of internal data-structures (esp. HTMLDefinition) + - Allow merging configurations. Thus, + a -> b -> default + c -> d -> default + becomes + a -> b -> c -> d -> default + Maybe allow more fine-grained tuning of this behavior. Alternatively, + encourage people to use short plist depths before building them up. + - Time PHPT tests + +ChildDef related (very boring) + - Abstract ChildDef_BlockQuote to work with all elements that only + allow blocks in them, required or optional + - Implement lenient <ruby> child validation + +Wontfix + - Non-lossy smart alternate character encoding transformations (unless + patch provided) + - Pretty-printing HTML: users can use Tidy on the output on entire page + - Native content compression, whitespace stripping: use gzip if this is + really important + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/VERSION b/library/ezyang/htmlpurifier/VERSION new file mode 100644 index 0000000000..1163055e28 --- /dev/null +++ b/library/ezyang/htmlpurifier/VERSION @@ -0,0 +1 @@ +4.7.0 \ No newline at end of file diff --git a/library/ezyang/htmlpurifier/WHATSNEW b/library/ezyang/htmlpurifier/WHATSNEW new file mode 100644 index 0000000000..4e5eb2b691 --- /dev/null +++ b/library/ezyang/htmlpurifier/WHATSNEW @@ -0,0 +1,4 @@ +HTML Purifier 4.7.0 is a bugfix release, collecting two years +worth of accumulated bug fixes. Highlighted bugfixes are updated +YouTube filter code, corrected rgb() CSS parsing, and one new +configuration option, %AutoFormat.RemoveEmpty.Predicate. diff --git a/library/ezyang/htmlpurifier/WYSIWYG b/library/ezyang/htmlpurifier/WYSIWYG new file mode 100644 index 0000000000..c518aacdd9 --- /dev/null +++ b/library/ezyang/htmlpurifier/WYSIWYG @@ -0,0 +1,20 @@ + +WYSIWYG - What You See Is What You Get + HTML Purifier: A Pretty Good Fit for TinyMCE and FCKeditor + +Javascript-based WYSIWYG editors, simply stated, are quite amazing. But I've +always been wary about using them due to security issues: they handle the +client-side magic, but once you've been served a piping hot load of unfiltered +HTML, what should be done then? In some situations, you can serve it uncleaned, +since you only offer these facilities to trusted(?) authors. + +Unfortunantely, for blog comments and anonymous input, BBCode, Textile and +other markup languages still reign supreme. Put simply: filtering HTML is +hard work, and these WYSIWYG authors don't offer anything to alleviate that +trouble. Therein lies the solution: + +HTML Purifier is perfect for filtering pure-HTML input from WYSIWYG editors. + +Enough said. + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/composer.json b/library/ezyang/htmlpurifier/composer.json new file mode 100644 index 0000000000..2f59d0fede --- /dev/null +++ b/library/ezyang/htmlpurifier/composer.json @@ -0,0 +1,22 @@ +{ + "name": "ezyang/htmlpurifier", + "description": "Standards compliant HTML filter written in PHP", + "type": "library", + "keywords": ["html"], + "homepage": "http://htmlpurifier.org/", + "license": "LGPL", + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "require": { + "php": ">=5.2" + }, + "autoload": { + "psr-0": { "HTMLPurifier": "library/" }, + "files": ["library/HTMLPurifier.composer.php"] + } +} diff --git a/library/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php b/library/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php new file mode 100644 index 0000000000..1cfec5d762 --- /dev/null +++ b/library/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php @@ -0,0 +1,91 @@ +<?php + +/** + * Decorator/extender XSLT processor specifically for HTML documents. + */ +class ConfigDoc_HTMLXSLTProcessor +{ + + /** + * Instance of XSLTProcessor + */ + protected $xsltProcessor; + + public function __construct($proc = false) + { + if ($proc === false) $proc = new XSLTProcessor(); + $this->xsltProcessor = $proc; + } + + /** + * @note Allows a string $xsl filename to be passed + */ + public function importStylesheet($xsl) + { + if (is_string($xsl)) { + $xsl_file = $xsl; + $xsl = new DOMDocument(); + $xsl->load($xsl_file); + } + return $this->xsltProcessor->importStylesheet($xsl); + } + + /** + * Transforms an XML file into compatible XHTML based on the stylesheet + * @param $xml XML DOM tree, or string filename + * @return string HTML output + * @todo Rename to transformToXHTML, as transformToHTML is misleading + */ + public function transformToHTML($xml) + { + if (is_string($xml)) { + $dom = new DOMDocument(); + $dom->load($xml); + } else { + $dom = $xml; + } + $out = $this->xsltProcessor->transformToXML($dom); + + // fudges for HTML backwards compatibility + // assumes that document is XHTML + $out = str_replace('/>', ' />', $out); // <br /> not <br/> + $out = str_replace(' xmlns=""', '', $out); // rm unnecessary xmlns + + if (class_exists('Tidy')) { + // cleanup output + $config = array( + 'indent' => true, + 'output-xhtml' => true, + 'wrap' => 80 + ); + $tidy = new Tidy; + $tidy->parseString($out, $config, 'utf8'); + $tidy->cleanRepair(); + $out = (string) $tidy; + } + + return $out; + } + + /** + * Bulk sets parameters for the XSL stylesheet + * @param array $options Associative array of options to set + */ + public function setParameters($options) + { + foreach ($options as $name => $value) { + $this->xsltProcessor->setParameter('', $name, $value); + } + } + + /** + * Forward any other calls to the XSLT processor + */ + public function __call($name, $arguments) + { + call_user_func_array(array($this->xsltProcessor, $name), $arguments); + } + +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/extras/FSTools.php b/library/ezyang/htmlpurifier/extras/FSTools.php new file mode 100644 index 0000000000..ce00763166 --- /dev/null +++ b/library/ezyang/htmlpurifier/extras/FSTools.php @@ -0,0 +1,164 @@ +<?php + +/** + * Filesystem tools not provided by default; can recursively create, copy + * and delete folders. Some template methods are provided for extensibility. + * + * @note This class must be instantiated to be used, although it does + * not maintain state. + */ +class FSTools +{ + + private static $singleton; + + /** + * Returns a global instance of FSTools + */ + public static function singleton() + { + if (empty(FSTools::$singleton)) FSTools::$singleton = new FSTools(); + return FSTools::$singleton; + } + + /** + * Sets our global singleton to something else; useful for overloading + * functions. + */ + public static function setSingleton($singleton) + { + FSTools::$singleton = $singleton; + } + + /** + * Recursively creates a directory + * @param string $folder Name of folder to create + * @note Adapted from the PHP manual comment 76612 + */ + public function mkdirr($folder) + { + $folders = preg_split("#[\\\\/]#", $folder); + $base = ''; + for($i = 0, $c = count($folders); $i < $c; $i++) { + if(empty($folders[$i])) { + if (!$i) { + // special case for root level + $base .= DIRECTORY_SEPARATOR; + } + continue; + } + $base .= $folders[$i]; + if(!is_dir($base)){ + $this->mkdir($base); + } + $base .= DIRECTORY_SEPARATOR; + } + } + + /** + * Copy a file, or recursively copy a folder and its contents; modified + * so that copied files, if PHP, have includes removed + * @note Adapted from http://aidanlister.com/repos/v/function.copyr.php + */ + public function copyr($source, $dest) + { + // Simple copy for a file + if (is_file($source)) { + return $this->copy($source, $dest); + } + // Make destination directory + if (!is_dir($dest)) { + $this->mkdir($dest); + } + // Loop through the folder + $dir = $this->dir($source); + while ( false !== ($entry = $dir->read()) ) { + // Skip pointers + if ($entry == '.' || $entry == '..') { + continue; + } + if (!$this->copyable($entry)) { + continue; + } + // Deep copy directories + if ($dest !== "$source/$entry") { + $this->copyr("$source/$entry", "$dest/$entry"); + } + } + // Clean up + $dir->close(); + return true; + } + + /** + * Overloadable function that tests a filename for copyability. By + * default, everything should be copied; you can restrict things to + * ignore hidden files, unreadable files, etc. This function + * applies to copyr(). + */ + public function copyable($file) + { + return true; + } + + /** + * Delete a file, or a folder and its contents + * @note Adapted from http://aidanlister.com/repos/v/function.rmdirr.php + */ + public function rmdirr($dirname) + { + // Sanity check + if (!$this->file_exists($dirname)) { + return false; + } + + // Simple delete for a file + if ($this->is_file($dirname) || $this->is_link($dirname)) { + return $this->unlink($dirname); + } + + // Loop through the folder + $dir = $this->dir($dirname); + while (false !== $entry = $dir->read()) { + // Skip pointers + if ($entry == '.' || $entry == '..') { + continue; + } + // Recurse + $this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry); + } + + // Clean up + $dir->close(); + return $this->rmdir($dirname); + } + + /** + * Recursively globs a directory. + */ + public function globr($dir, $pattern, $flags = NULL) + { + $files = $this->glob("$dir/$pattern", $flags); + if ($files === false) $files = array(); + $sub_dirs = $this->glob("$dir/*", GLOB_ONLYDIR); + if ($sub_dirs === false) $sub_dirs = array(); + foreach ($sub_dirs as $sub_dir) { + $sub_files = $this->globr($sub_dir, $pattern, $flags); + $files = array_merge($files, $sub_files); + } + return $files; + } + + /** + * Allows for PHP functions to be called and be stubbed. + * @warning This function will not work for functions that need + * to pass references; manually define a stub function for those. + */ + public function __call($name, $args) + { + return call_user_func_array($name, $args); + } + +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/extras/FSTools/File.php b/library/ezyang/htmlpurifier/extras/FSTools/File.php new file mode 100644 index 0000000000..6453a7a450 --- /dev/null +++ b/library/ezyang/htmlpurifier/extras/FSTools/File.php @@ -0,0 +1,141 @@ +<?php + +/** + * Represents a file in the filesystem + * + * @warning Be sure to distinguish between get() and write() versus + * read() and put(), the former operates on the entire file, while + * the latter operates on a handle. + */ +class FSTools_File +{ + + /** Filename of file this object represents */ + protected $name; + + /** Handle for the file */ + protected $handle = false; + + /** Instance of FSTools for interfacing with filesystem */ + protected $fs; + + /** + * Filename of file you wish to instantiate. + * @note This file need not exist + */ + public function __construct($name, $fs = false) + { + $this->name = $name; + $this->fs = $fs ? $fs : FSTools::singleton(); + } + + /** Returns the filename of the file. */ + public function getName() {return $this->name;} + + /** Returns directory of the file without trailing slash */ + public function getDirectory() {return $this->fs->dirname($this->name);} + + /** + * Retrieves the contents of a file + * @todo Throw an exception if file doesn't exist + */ + public function get() + { + return $this->fs->file_get_contents($this->name); + } + + /** Writes contents to a file, creates new file if necessary */ + public function write($contents) + { + return $this->fs->file_put_contents($this->name, $contents); + } + + /** Deletes the file */ + public function delete() + { + return $this->fs->unlink($this->name); + } + + /** Returns true if file exists and is a file. */ + public function exists() + { + return $this->fs->is_file($this->name); + } + + /** Returns last file modification time */ + public function getMTime() + { + return $this->fs->filemtime($this->name); + } + + /** + * Chmod a file + * @note We ignore errors because of some weird owner trickery due + * to SVN duality + */ + public function chmod($octal_code) + { + return @$this->fs->chmod($this->name, $octal_code); + } + + /** Opens file's handle */ + public function open($mode) + { + if ($this->handle) $this->close(); + $this->handle = $this->fs->fopen($this->name, $mode); + return true; + } + + /** Closes file's handle */ + public function close() + { + if (!$this->handle) return false; + $status = $this->fs->fclose($this->handle); + $this->handle = false; + return $status; + } + + /** Retrieves a line from an open file, with optional max length $length */ + public function getLine($length = null) + { + if (!$this->handle) $this->open('r'); + if ($length === null) return $this->fs->fgets($this->handle); + else return $this->fs->fgets($this->handle, $length); + } + + /** Retrieves a character from an open file */ + public function getChar() + { + if (!$this->handle) $this->open('r'); + return $this->fs->fgetc($this->handle); + } + + /** Retrieves an $length bytes of data from an open data */ + public function read($length) + { + if (!$this->handle) $this->open('r'); + return $this->fs->fread($this->handle, $length); + } + + /** Writes to an open file */ + public function put($string) + { + if (!$this->handle) $this->open('a'); + return $this->fs->fwrite($this->handle, $string); + } + + /** Returns TRUE if the end of the file has been reached */ + public function eof() + { + if (!$this->handle) return true; + return $this->fs->feof($this->handle); + } + + public function __destruct() + { + if ($this->handle) $this->close(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php new file mode 100644 index 0000000000..4016d8afd1 --- /dev/null +++ b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php @@ -0,0 +1,11 @@ +<?php + +/** + * This is a stub include that automatically configures the include path. + */ + +set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() ); +require_once 'HTMLPurifierExtras.php'; +require_once 'HTMLPurifierExtras.autoload.php'; + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php new file mode 100644 index 0000000000..de4a8aaafe --- /dev/null +++ b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.autoload.php @@ -0,0 +1,26 @@ +<?php + +/** + * @file + * Convenience file that registers autoload handler for HTML Purifier. + * + * @warning + * This autoloader does not contain the compatibility code seen in + * HTMLPurifier_Bootstrap; the user is expected to make any necessary + * changes to use this library. + */ + +if (function_exists('spl_autoload_register')) { + spl_autoload_register(array('HTMLPurifierExtras', 'autoload')); + if (function_exists('__autoload')) { + // Be polite and ensure that userland autoload gets retained + spl_autoload_register('__autoload'); + } +} elseif (!function_exists('__autoload')) { + function __autoload($class) + { + return HTMLPurifierExtras::autoload($class); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php new file mode 100644 index 0000000000..35c2ca7e72 --- /dev/null +++ b/library/ezyang/htmlpurifier/extras/HTMLPurifierExtras.php @@ -0,0 +1,31 @@ +<?php + +/** + * Meta-class for HTML Purifier's extra class hierarchies, similar to + * HTMLPurifier_Bootstrap. + */ +class HTMLPurifierExtras +{ + + public static function autoload($class) + { + $path = HTMLPurifierExtras::getPath($class); + if (!$path) return false; + require $path; + return true; + } + + public static function getPath($class) + { + if ( + strncmp('FSTools', $class, 7) !== 0 && + strncmp('ConfigDoc', $class, 9) !== 0 + ) return false; + // Custom implementations can go here + // Standard implementation: + return str_replace('_', '/', $class) . '.php'; + } + +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/extras/README b/library/ezyang/htmlpurifier/extras/README new file mode 100644 index 0000000000..4bfece79ea --- /dev/null +++ b/library/ezyang/htmlpurifier/extras/README @@ -0,0 +1,32 @@ + +HTML Purifier Extras + The Method Behind The Madness! + +The extras/ folder in HTML Purifier contains--you guessed it--extra things +for HTML Purifier. Specifically, these are two extra libraries called +FSTools and ConfigSchema. They're extra for a reason: you don't need them +if you're using HTML Purifier for normal usage: filtering HTML. However, +if you're a developer, and would like to test HTML Purifier, or need to +use one of HTML Purifier's maintenance scripts, chances are they'll need +these libraries. Who knows: maybe you'll find them useful too! + +Here are the libraries: + + +FSTools +------- + +Short for File System Tools, this is a poor-man's object-oriented wrapper for +the filesystem. It currently consists of two classes: + +- FSTools: This is a singleton that contains a manner of useful functions + such as recursive glob, directory removal, etc, as well as the ability + to call arbitrary native PHP functions through it like $FS->fopen(...). + This makes it a lot simpler to mock these filesystem calls for unit testing. + +- FSTools_File: This object represents a single file, and has almost any + method imaginable one would need. + +Check the files themselves for more information. + + vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier.auto.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.auto.php similarity index 100% rename from library/HTMLPurifier.auto.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.auto.php diff --git a/library/HTMLPurifier.autoload.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php similarity index 70% rename from library/HTMLPurifier.autoload.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php index 8d40176406..c3ea67e814 100644 --- a/library/HTMLPurifier.autoload.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier.autoload.php @@ -3,6 +3,7 @@ /** * @file * Convenience file that registers autoload handler for HTML Purifier. + * It also does some sanity checks. */ if (function_exists('spl_autoload_register') && function_exists('spl_autoload_unregister')) { @@ -13,9 +14,14 @@ if (function_exists('spl_autoload_register') && function_exists('spl_autoload_un spl_autoload_register('__autoload'); } } elseif (!function_exists('__autoload')) { - function __autoload($class) { + function __autoload($class) + { return HTMLPurifier_Bootstrap::autoload($class); } } +if (ini_get('zend.ze1_compatibility_mode')) { + trigger_error("HTML Purifier is not compatible with zend.ze1_compatibility_mode; please turn it off", E_USER_ERROR); +} + // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier.composer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.composer.php new file mode 100644 index 0000000000..52acc56b0d --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier.composer.php @@ -0,0 +1,4 @@ +<?php +if (!defined('HTMLPURIFIER_PREFIX')) { + define('HTMLPURIFIER_PREFIX', dirname(__FILE__)); +} diff --git a/library/HTMLPurifier.func.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.func.php similarity index 67% rename from library/HTMLPurifier.func.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.func.php index 56a55b2fed..64b140bec2 100644 --- a/library/HTMLPurifier.func.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier.func.php @@ -8,11 +8,13 @@ /** * Purify HTML. - * @param $html String HTML to purify - * @param $config Configuration to use, can be any value accepted by + * @param string $html String HTML to purify + * @param mixed $config Configuration to use, can be any value accepted by * HTMLPurifier_Config::create() + * @return string */ -function HTMLPurifier($html, $config = null) { +function HTMLPurifier($html, $config = null) +{ static $purifier = false; if (!$purifier) { $purifier = new HTMLPurifier(); diff --git a/library/HTMLPurifier.includes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.includes.php similarity index 91% rename from library/HTMLPurifier.includes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.includes.php index 2ed0f0c17f..fdb58c2d37 100644 --- a/library/HTMLPurifier.includes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier.includes.php @@ -7,7 +7,7 @@ * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS * FILE, changes will be overwritten the next time the script is run. * - * @version 4.1.1 + * @version 4.7.0 * * @warning * You must *not* include any other HTML Purifier files before this file, @@ -19,6 +19,7 @@ */ require 'HTMLPurifier.php'; +require 'HTMLPurifier/Arborize.php'; require 'HTMLPurifier/AttrCollections.php'; require 'HTMLPurifier/AttrDef.php'; require 'HTMLPurifier/AttrTransform.php'; @@ -54,9 +55,11 @@ require 'HTMLPurifier/Language.php'; require 'HTMLPurifier/LanguageFactory.php'; require 'HTMLPurifier/Length.php'; require 'HTMLPurifier/Lexer.php'; +require 'HTMLPurifier/Node.php'; require 'HTMLPurifier/PercentEncoder.php'; require 'HTMLPurifier/PropertyList.php'; require 'HTMLPurifier/PropertyListIterator.php'; +require 'HTMLPurifier/Queue.php'; require 'HTMLPurifier/Strategy.php'; require 'HTMLPurifier/StringHash.php'; require 'HTMLPurifier/StringHashParser.php'; @@ -72,7 +75,9 @@ require 'HTMLPurifier/URISchemeRegistry.php'; require 'HTMLPurifier/UnitConverter.php'; require 'HTMLPurifier/VarParser.php'; require 'HTMLPurifier/VarParserException.php'; +require 'HTMLPurifier/Zipper.php'; require 'HTMLPurifier/AttrDef/CSS.php'; +require 'HTMLPurifier/AttrDef/Clone.php'; require 'HTMLPurifier/AttrDef/Enum.php'; require 'HTMLPurifier/AttrDef/Integer.php'; require 'HTMLPurifier/AttrDef/Lang.php'; @@ -90,6 +95,7 @@ require 'HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php'; require 'HTMLPurifier/AttrDef/CSS/Filter.php'; require 'HTMLPurifier/AttrDef/CSS/Font.php'; require 'HTMLPurifier/AttrDef/CSS/FontFamily.php'; +require 'HTMLPurifier/AttrDef/CSS/Ident.php'; require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php'; require 'HTMLPurifier/AttrDef/CSS/Length.php'; require 'HTMLPurifier/AttrDef/CSS/ListStyle.php'; @@ -125,14 +131,17 @@ require 'HTMLPurifier/AttrTransform/Lang.php'; require 'HTMLPurifier/AttrTransform/Length.php'; require 'HTMLPurifier/AttrTransform/Name.php'; require 'HTMLPurifier/AttrTransform/NameSync.php'; +require 'HTMLPurifier/AttrTransform/Nofollow.php'; require 'HTMLPurifier/AttrTransform/SafeEmbed.php'; require 'HTMLPurifier/AttrTransform/SafeObject.php'; require 'HTMLPurifier/AttrTransform/SafeParam.php'; require 'HTMLPurifier/AttrTransform/ScriptRequired.php'; +require 'HTMLPurifier/AttrTransform/TargetBlank.php'; require 'HTMLPurifier/AttrTransform/Textarea.php'; require 'HTMLPurifier/ChildDef/Chameleon.php'; require 'HTMLPurifier/ChildDef/Custom.php'; require 'HTMLPurifier/ChildDef/Empty.php'; +require 'HTMLPurifier/ChildDef/List.php'; require 'HTMLPurifier/ChildDef/Required.php'; require 'HTMLPurifier/ChildDef/Optional.php'; require 'HTMLPurifier/ChildDef/StrictBlockquote.php'; @@ -147,10 +156,12 @@ require 'HTMLPurifier/HTMLModule/CommonAttributes.php'; require 'HTMLPurifier/HTMLModule/Edit.php'; require 'HTMLPurifier/HTMLModule/Forms.php'; require 'HTMLPurifier/HTMLModule/Hypertext.php'; +require 'HTMLPurifier/HTMLModule/Iframe.php'; require 'HTMLPurifier/HTMLModule/Image.php'; require 'HTMLPurifier/HTMLModule/Legacy.php'; require 'HTMLPurifier/HTMLModule/List.php'; require 'HTMLPurifier/HTMLModule/Name.php'; +require 'HTMLPurifier/HTMLModule/Nofollow.php'; require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php'; require 'HTMLPurifier/HTMLModule/Object.php'; require 'HTMLPurifier/HTMLModule/Presentation.php'; @@ -158,10 +169,12 @@ require 'HTMLPurifier/HTMLModule/Proprietary.php'; require 'HTMLPurifier/HTMLModule/Ruby.php'; require 'HTMLPurifier/HTMLModule/SafeEmbed.php'; require 'HTMLPurifier/HTMLModule/SafeObject.php'; +require 'HTMLPurifier/HTMLModule/SafeScripting.php'; require 'HTMLPurifier/HTMLModule/Scripting.php'; require 'HTMLPurifier/HTMLModule/StyleAttribute.php'; require 'HTMLPurifier/HTMLModule/Tables.php'; require 'HTMLPurifier/HTMLModule/Target.php'; +require 'HTMLPurifier/HTMLModule/TargetBlank.php'; require 'HTMLPurifier/HTMLModule/Text.php'; require 'HTMLPurifier/HTMLModule/Tidy.php'; require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; @@ -180,6 +193,9 @@ require 'HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php'; require 'HTMLPurifier/Injector/SafeObject.php'; require 'HTMLPurifier/Lexer/DOMLex.php'; require 'HTMLPurifier/Lexer/DirectLex.php'; +require 'HTMLPurifier/Node/Comment.php'; +require 'HTMLPurifier/Node/Element.php'; +require 'HTMLPurifier/Node/Text.php'; require 'HTMLPurifier/Strategy/Composite.php'; require 'HTMLPurifier/Strategy/Core.php'; require 'HTMLPurifier/Strategy/FixNesting.php'; @@ -196,10 +212,13 @@ require 'HTMLPurifier/Token/Start.php'; require 'HTMLPurifier/Token/Text.php'; require 'HTMLPurifier/URIFilter/DisableExternal.php'; require 'HTMLPurifier/URIFilter/DisableExternalResources.php'; +require 'HTMLPurifier/URIFilter/DisableResources.php'; require 'HTMLPurifier/URIFilter/HostBlacklist.php'; require 'HTMLPurifier/URIFilter/MakeAbsolute.php'; require 'HTMLPurifier/URIFilter/Munge.php'; +require 'HTMLPurifier/URIFilter/SafeIframe.php'; require 'HTMLPurifier/URIScheme/data.php'; +require 'HTMLPurifier/URIScheme/file.php'; require 'HTMLPurifier/URIScheme/ftp.php'; require 'HTMLPurifier/URIScheme/http.php'; require 'HTMLPurifier/URIScheme/https.php'; diff --git a/library/HTMLPurifier.kses.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.kses.php similarity index 96% rename from library/HTMLPurifier.kses.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.kses.php index 3143feb17f..752290077f 100644 --- a/library/HTMLPurifier.kses.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier.kses.php @@ -7,7 +7,8 @@ require_once dirname(__FILE__) . '/HTMLPurifier.auto.php'; -function kses($string, $allowed_html, $allowed_protocols = null) { +function kses($string, $allowed_html, $allowed_protocols = null) +{ $config = HTMLPurifier_Config::createDefault(); $allowed_elements = array(); $allowed_attributes = array(); @@ -19,7 +20,6 @@ function kses($string, $allowed_html, $allowed_protocols = null) { } $config->set('HTML.AllowedElements', $allowed_elements); $config->set('HTML.AllowedAttributes', $allowed_attributes); - $allowed_schemes = array(); if ($allowed_protocols !== null) { $config->set('URI.AllowedSchemes', $allowed_protocols); } diff --git a/library/HTMLPurifier.path.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.path.php similarity index 100% rename from library/HTMLPurifier.path.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.path.php diff --git a/library/HTMLPurifier.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.php similarity index 66% rename from library/HTMLPurifier.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.php index ba2c7b3067..c6041bc113 100644 --- a/library/HTMLPurifier.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier.php @@ -19,7 +19,7 @@ */ /* - HTML Purifier 4.1.1 - Standards Compliant HTML Filtering + HTML Purifier 4.7.0 - Standards Compliant HTML Filtering Copyright (C) 2006-2008 Edward Z. Yang This library is free software; you can redistribute it and/or @@ -54,66 +54,97 @@ class HTMLPurifier { - /** Version of HTML Purifier */ - public $version = '4.1.1'; - - /** Constant with version of HTML Purifier */ - const VERSION = '4.1.1'; - - /** Global configuration object */ - public $config; - - /** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */ - private $filters = array(); - - /** Single instance of HTML Purifier */ - private static $instance; - - protected $strategy, $generator; + /** + * Version of HTML Purifier. + * @type string + */ + public $version = '4.7.0'; /** - * Resultant HTMLPurifier_Context of last run purification. Is an array - * of contexts if the last called method was purifyArray(). + * Constant with version of HTML Purifier. + */ + const VERSION = '4.7.0'; + + /** + * Global configuration object. + * @type HTMLPurifier_Config + */ + public $config; + + /** + * Array of extra filter objects to run on HTML, + * for backwards compatibility. + * @type HTMLPurifier_Filter[] + */ + private $filters = array(); + + /** + * Single instance of HTML Purifier. + * @type HTMLPurifier + */ + private static $instance; + + /** + * @type HTMLPurifier_Strategy_Core + */ + protected $strategy; + + /** + * @type HTMLPurifier_Generator + */ + protected $generator; + + /** + * Resultant context of last run purification. + * Is an array of contexts if the last called method was purifyArray(). + * @type HTMLPurifier_Context */ public $context; /** * Initializes the purifier. - * @param $config Optional HTMLPurifier_Config object for all instances of - * the purifier, if omitted, a default configuration is - * supplied (which can be overridden on a per-use basis). + * + * @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object + * for all instances of the purifier, if omitted, a default + * configuration is supplied (which can be overridden on a + * per-use basis). * The parameter can also be any type that * HTMLPurifier_Config::create() supports. */ - public function __construct($config = null) { - + public function __construct($config = null) + { $this->config = HTMLPurifier_Config::create($config); - - $this->strategy = new HTMLPurifier_Strategy_Core(); - + $this->strategy = new HTMLPurifier_Strategy_Core(); } /** * Adds a filter to process the output. First come first serve - * @param $filter HTMLPurifier_Filter object + * + * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object */ - public function addFilter($filter) { - trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING); + public function addFilter($filter) + { + trigger_error( + 'HTMLPurifier->addFilter() is deprecated, use configuration directives' . + ' in the Filter namespace or Filter.Custom', + E_USER_WARNING + ); $this->filters[] = $filter; } /** * Filters an HTML snippet/document to be XSS-free and standards-compliant. * - * @param $html String of HTML to purify - * @param $config HTMLPurifier_Config object for this operation, if omitted, - * defaults to the config object specified during this + * @param string $html String of HTML to purify + * @param HTMLPurifier_Config $config Config object for this operation, + * if omitted, defaults to the config object specified during this * object's construction. The parameter can also be any type * that HTMLPurifier_Config::create() supports. - * @return Purified HTML + * + * @return string Purified HTML */ - public function purify($html, $config = null) { - + public function purify($html, $config = null) + { // :TODO: make the config merge in, instead of replace $config = $config ? HTMLPurifier_Config::create($config) : $this->config; @@ -151,8 +182,12 @@ class HTMLPurifier unset($filter_flags['Custom']); $filters = array(); foreach ($filter_flags as $filter => $flag) { - if (!$flag) continue; - if (strpos($filter, '.') !== false) continue; + if (!$flag) { + continue; + } + if (strpos($filter, '.') !== false) { + continue; + } $class = "HTMLPurifier_Filter_$filter"; $filters[] = new $class; } @@ -175,9 +210,12 @@ class HTMLPurifier // list of un-purified tokens $lexer->tokenizeHTML( // un-purified HTML - $html, $config, $context + $html, + $config, + $context ), - $config, $context + $config, + $context ) ); @@ -192,11 +230,15 @@ class HTMLPurifier /** * Filters an array of HTML snippets - * @param $config Optional HTMLPurifier_Config object for this operation. + * + * @param string[] $array_of_html Array of html snippets + * @param HTMLPurifier_Config $config Optional config object for this operation. * See HTMLPurifier::purify() for more details. - * @return Array of purified HTML + * + * @return string[] Array of purified HTML */ - public function purifyArray($array_of_html, $config = null) { + public function purifyArray($array_of_html, $config = null) + { $context_array = array(); foreach ($array_of_html as $key => $html) { $array_of_html[$key] = $this->purify($html, $config); @@ -208,11 +250,16 @@ class HTMLPurifier /** * Singleton for enforcing just one HTML Purifier in your system - * @param $prototype Optional prototype HTMLPurifier instance to - * overload singleton with, or HTMLPurifier_Config - * instance to configure the generated version with. + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { if (!self::$instance || $prototype) { if ($prototype instanceof HTMLPurifier) { self::$instance = $prototype; @@ -226,12 +273,20 @@ class HTMLPurifier } /** + * Singleton for enforcing just one HTML Purifier in your system + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier * @note Backwards compatibility, see instance() */ - public static function getInstance($prototype = null) { + public static function getInstance($prototype = null) + { return HTMLPurifier::instance($prototype); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier.safe-includes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php similarity index 91% rename from library/HTMLPurifier.safe-includes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php index 6402de0458..9dea6d1ed5 100644 --- a/library/HTMLPurifier.safe-includes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php @@ -13,6 +13,7 @@ $__dir = dirname(__FILE__); require_once $__dir . '/HTMLPurifier.php'; +require_once $__dir . '/HTMLPurifier/Arborize.php'; require_once $__dir . '/HTMLPurifier/AttrCollections.php'; require_once $__dir . '/HTMLPurifier/AttrDef.php'; require_once $__dir . '/HTMLPurifier/AttrTransform.php'; @@ -48,9 +49,11 @@ require_once $__dir . '/HTMLPurifier/Language.php'; require_once $__dir . '/HTMLPurifier/LanguageFactory.php'; require_once $__dir . '/HTMLPurifier/Length.php'; require_once $__dir . '/HTMLPurifier/Lexer.php'; +require_once $__dir . '/HTMLPurifier/Node.php'; require_once $__dir . '/HTMLPurifier/PercentEncoder.php'; require_once $__dir . '/HTMLPurifier/PropertyList.php'; require_once $__dir . '/HTMLPurifier/PropertyListIterator.php'; +require_once $__dir . '/HTMLPurifier/Queue.php'; require_once $__dir . '/HTMLPurifier/Strategy.php'; require_once $__dir . '/HTMLPurifier/StringHash.php'; require_once $__dir . '/HTMLPurifier/StringHashParser.php'; @@ -66,7 +69,9 @@ require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php'; require_once $__dir . '/HTMLPurifier/UnitConverter.php'; require_once $__dir . '/HTMLPurifier/VarParser.php'; require_once $__dir . '/HTMLPurifier/VarParserException.php'; +require_once $__dir . '/HTMLPurifier/Zipper.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php'; +require_once $__dir . '/HTMLPurifier/AttrDef/Clone.php'; require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php'; require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php'; require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php'; @@ -84,6 +89,7 @@ require_once $__dir . '/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/FontFamily.php'; +require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Ident.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Length.php'; require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php'; @@ -119,14 +125,17 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php'; +require_once $__dir . '/HTMLPurifier/ChildDef/List.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Required.php'; require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php'; require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php'; @@ -141,10 +150,12 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/Iframe.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/List.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/Nofollow.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php'; @@ -152,10 +163,12 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/SafeScripting.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php'; @@ -174,6 +187,9 @@ require_once $__dir . '/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php'; require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php'; require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php'; require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php'; +require_once $__dir . '/HTMLPurifier/Node/Comment.php'; +require_once $__dir . '/HTMLPurifier/Node/Element.php'; +require_once $__dir . '/HTMLPurifier/Node/Text.php'; require_once $__dir . '/HTMLPurifier/Strategy/Composite.php'; require_once $__dir . '/HTMLPurifier/Strategy/Core.php'; require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php'; @@ -190,10 +206,13 @@ require_once $__dir . '/HTMLPurifier/Token/Start.php'; require_once $__dir . '/HTMLPurifier/Token/Text.php'; require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php'; require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php'; +require_once $__dir . '/HTMLPurifier/URIFilter/DisableResources.php'; require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php'; require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php'; require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php'; +require_once $__dir . '/HTMLPurifier/URIFilter/SafeIframe.php'; require_once $__dir . '/HTMLPurifier/URIScheme/data.php'; +require_once $__dir . '/HTMLPurifier/URIScheme/file.php'; require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php'; require_once $__dir . '/HTMLPurifier/URIScheme/http.php'; require_once $__dir . '/HTMLPurifier/URIScheme/https.php'; diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php new file mode 100644 index 0000000000..9e6617be5d --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php @@ -0,0 +1,71 @@ +<?php + +/** + * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node, + * and back again. + * + * @note This transformation is not an equivalence. We mutate the input + * token stream to make it so; see all [MUT] markers in code. + */ +class HTMLPurifier_Arborize +{ + public static function arborize($tokens, $config, $context) { + $definition = $config->getHTMLDefinition(); + $parent = new HTMLPurifier_Token_Start($definition->info_parent); + $stack = array($parent->toNode()); + foreach ($tokens as $token) { + $token->skip = null; // [MUT] + $token->carryover = null; // [MUT] + if ($token instanceof HTMLPurifier_Token_End) { + $token->start = null; // [MUT] + $r = array_pop($stack); + assert($r->name === $token->name); + assert(empty($token->attr)); + $r->endCol = $token->col; + $r->endLine = $token->line; + $r->endArmor = $token->armor; + continue; + } + $node = $token->toNode(); + $stack[count($stack)-1]->children[] = $node; + if ($token instanceof HTMLPurifier_Token_Start) { + $stack[] = $node; + } + } + assert(count($stack) == 1); + return $stack[0]; + } + + public static function flatten($node, $config, $context) { + $level = 0; + $nodes = array($level => new HTMLPurifier_Queue(array($node))); + $closingTokens = array(); + $tokens = array(); + do { + while (!$nodes[$level]->isEmpty()) { + $node = $nodes[$level]->shift(); // FIFO + list($start, $end) = $node->toTokenPair(); + if ($level > 0) { + $tokens[] = $start; + } + if ($end !== NULL) { + $closingTokens[$level][] = $end; + } + if ($node instanceof HTMLPurifier_Node_Element) { + $level++; + $nodes[$level] = new HTMLPurifier_Queue(); + foreach ($node->children as $childNode) { + $nodes[$level]->push($childNode); + } + } + } + $level--; + if ($level && isset($closingTokens[$level])) { + while ($token = array_pop($closingTokens[$level])) { + $tokens[] = $token; + } + } + } while ($level > 0); + return $tokens; + } +} diff --git a/library/HTMLPurifier/AttrCollections.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php similarity index 74% rename from library/HTMLPurifier/AttrCollections.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php index 555b86d042..4f6c2e39a2 100644 --- a/library/HTMLPurifier/AttrCollections.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php @@ -8,7 +8,8 @@ class HTMLPurifier_AttrCollections { /** - * Associative array of attribute collections, indexed by name + * Associative array of attribute collections, indexed by name. + * @type array */ public $info = array(); @@ -16,10 +17,11 @@ class HTMLPurifier_AttrCollections * Performs all expansions on internal data for use by other inclusions * It also collects all attribute collection extensions from * modules - * @param $attr_types HTMLPurifier_AttrTypes instance - * @param $modules Hash array of HTMLPurifier_HTMLModule members + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members */ - public function __construct($attr_types, $modules) { + public function __construct($attr_types, $modules) + { // load extensions from the modules foreach ($modules as $module) { foreach ($module->attr_collections as $coll_i => $coll) { @@ -30,7 +32,9 @@ class HTMLPurifier_AttrCollections if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) { // merge in includes $this->info[$coll_i][$attr_i] = array_merge( - $this->info[$coll_i][$attr_i], $attr); + $this->info[$coll_i][$attr_i], + $attr + ); continue; } $this->info[$coll_i][$attr_i] = $attr; @@ -49,20 +53,29 @@ class HTMLPurifier_AttrCollections /** * Takes a reference to an attribute associative array and performs * all inclusions specified by the zero index. - * @param &$attr Reference to attribute array + * @param array &$attr Reference to attribute array */ - public function performInclusions(&$attr) { - if (!isset($attr[0])) return; + public function performInclusions(&$attr) + { + if (!isset($attr[0])) { + return; + } $merge = $attr[0]; $seen = array(); // recursion guard // loop through all the inclusions for ($i = 0; isset($merge[$i]); $i++) { - if (isset($seen[$merge[$i]])) continue; + if (isset($seen[$merge[$i]])) { + continue; + } $seen[$merge[$i]] = true; // foreach attribute of the inclusion, copy it over - if (!isset($this->info[$merge[$i]])) continue; + if (!isset($this->info[$merge[$i]])) { + continue; + } foreach ($this->info[$merge[$i]] as $key => $value) { - if (isset($attr[$key])) continue; // also catches more inclusions + if (isset($attr[$key])) { + continue; + } // also catches more inclusions $attr[$key] = $value; } if (isset($this->info[$merge[$i]][0])) { @@ -76,20 +89,24 @@ class HTMLPurifier_AttrCollections /** * Expands all string identifiers in an attribute array by replacing * them with the appropriate values inside HTMLPurifier_AttrTypes - * @param &$attr Reference to attribute array - * @param $attr_types HTMLPurifier_AttrTypes instance + * @param array &$attr Reference to attribute array + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance */ - public function expandIdentifiers(&$attr, $attr_types) { - + public function expandIdentifiers(&$attr, $attr_types) + { // because foreach will process new elements we add, make sure we // skip duplicates $processed = array(); foreach ($attr as $def_i => $def) { // skip inclusions - if ($def_i === 0) continue; + if ($def_i === 0) { + continue; + } - if (isset($processed[$def_i])) continue; + if (isset($processed[$def_i])) { + continue; + } // determine whether or not attribute is required if ($required = (strpos($def_i, '*') !== false)) { @@ -120,9 +137,7 @@ class HTMLPurifier_AttrCollections unset($attr[$def_i]); } } - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php similarity index 74% rename from library/HTMLPurifier/AttrDef.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php index b2e4f36c5d..5ac06522b9 100644 --- a/library/HTMLPurifier/AttrDef.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php @@ -14,23 +14,25 @@ abstract class HTMLPurifier_AttrDef { /** - * Tells us whether or not an HTML attribute is minimized. Has no - * meaning in other contexts. + * Tells us whether or not an HTML attribute is minimized. + * Has no meaning in other contexts. + * @type bool */ public $minimized = false; /** - * Tells us whether or not an HTML attribute is required. Has no - * meaning in other contexts + * Tells us whether or not an HTML attribute is required. + * Has no meaning in other contexts + * @type bool */ public $required = false; /** * Validates and cleans passed string according to a definition. * - * @param $string String to be validated and cleaned. - * @param $config Mandatory HTMLPurifier_Config object. - * @param $context Mandatory HTMLPurifier_AttrContext object. + * @param string $string String to be validated and cleaned. + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object. */ abstract public function validate($string, $config, $context); @@ -55,7 +57,8 @@ abstract class HTMLPurifier_AttrDef * parsing XML, thus, this behavior may still be correct. We * assume that newlines have been normalized. */ - public function parseCDATA($string) { + public function parseCDATA($string) + { $string = trim($string); $string = str_replace(array("\n", "\t", "\r"), ' ', $string); return $string; @@ -63,10 +66,11 @@ abstract class HTMLPurifier_AttrDef /** * Factory method for creating this class from a string. - * @param $string String construction info - * @return Created AttrDef object corresponding to $string + * @param string $string String construction info + * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string */ - public function make($string) { + public function make($string) + { // default implementation, return a flyweight of this object. // If $string has an effect on the returned object (i.e. you // need to overload this method), it is best @@ -77,16 +81,20 @@ abstract class HTMLPurifier_AttrDef /** * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work * properly. THIS IS A HACK! + * @param string $string a CSS colour definition + * @return string */ - protected function mungeRgb($string) { + protected function mungeRgb($string) + { return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string); } /** - * Parses a possibly escaped CSS string and returns the "pure" + * Parses a possibly escaped CSS string and returns the "pure" * version of it. */ - protected function expandCSSEscape($string) { + protected function expandCSSEscape($string) + { // flexibly parse it $ret = ''; for ($i = 0, $c = strlen($string); $i < $c; $i++) { @@ -99,25 +107,32 @@ abstract class HTMLPurifier_AttrDef if (ctype_xdigit($string[$i])) { $code = $string[$i]; for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { - if (!ctype_xdigit($string[$i])) break; + if (!ctype_xdigit($string[$i])) { + break; + } $code .= $string[$i]; } // We have to be extremely careful when adding // new characters, to make sure we're not breaking // the encoding. $char = HTMLPurifier_Encoder::unichr(hexdec($code)); - if (HTMLPurifier_Encoder::cleanUTF8($char) === '') continue; + if (HTMLPurifier_Encoder::cleanUTF8($char) === '') { + continue; + } $ret .= $char; - if ($i < $c && trim($string[$i]) !== '') $i--; + if ($i < $c && trim($string[$i]) !== '') { + $i--; + } + continue; + } + if ($string[$i] === "\n") { continue; } - if ($string[$i] === "\n") continue; } $ret .= $string[$i]; } return $ret; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php similarity index 77% rename from library/HTMLPurifier/AttrDef/CSS.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php index 953e706755..02c1641fb2 100644 --- a/library/HTMLPurifier/AttrDef/CSS.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php @@ -14,8 +14,14 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef { - public function validate($css, $config, $context) { - + /** + * @param string $css + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($css, $config, $context) + { $css = $this->parseCDATA($css); $definition = $config->getCSSDefinition(); @@ -36,34 +42,47 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef $context->register('CurrentCSSProperty', $property); foreach ($declarations as $declaration) { - if (!$declaration) continue; - if (!strpos($declaration, ':')) continue; + if (!$declaration) { + continue; + } + if (!strpos($declaration, ':')) { + continue; + } list($property, $value) = explode(':', $declaration, 2); $property = trim($property); - $value = trim($value); + $value = trim($value); $ok = false; do { if (isset($definition->info[$property])) { $ok = true; break; } - if (ctype_lower($property)) break; + if (ctype_lower($property)) { + break; + } $property = strtolower($property); if (isset($definition->info[$property])) { $ok = true; break; } - } while(0); - if (!$ok) continue; + } while (0); + if (!$ok) { + continue; + } // inefficient call, since the validator will do this again if (strtolower(trim($value)) !== 'inherit') { // inherit works for everything (but only on the base property) $result = $definition->info[$property]->validate( - $value, $config, $context ); + $value, + $config, + $context + ); } else { $result = 'inherit'; } - if ($result === false) continue; + if ($result === false) { + continue; + } $propvalues[$property] = $result; } diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php new file mode 100644 index 0000000000..af2b83dff8 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php @@ -0,0 +1,34 @@ +<?php + +class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number +{ + + public function __construct() + { + parent::__construct(false); // opacity is non-negative, but we will clamp it + } + + /** + * @param string $number + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function validate($number, $config, $context) + { + $result = parent::validate($number, $config, $context); + if ($result === false) { + return $result; + } + $float = (float)$result; + if ($float < 0.0) { + $result = '0'; + } + if ($float > 1.0) { + $result = '1'; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Background.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php similarity index 63% rename from library/HTMLPurifier/AttrDef/CSS/Background.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php index 3a3d20cd6a..7f1ea3b0f1 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Background.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php @@ -9,11 +9,16 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef /** * Local copy of component validators. + * @type HTMLPurifier_AttrDef[] * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl. */ protected $info; - public function __construct($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { $def = $config->getCSSDefinition(); $this->info['background-color'] = $def->info['background-color']; $this->info['background-image'] = $def->info['background-image']; @@ -22,40 +27,55 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef $this->info['background-position'] = $def->info['background-position']; } - public function validate($string, $config, $context) { - + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { // regular pre-processing $string = $this->parseCDATA($string); - if ($string === '') return false; + if ($string === '') { + return false; + } // munge rgb() decl if necessary $string = $this->mungeRgb($string); // assumes URI doesn't have spaces in it - $bits = explode(' ', strtolower($string)); // bits to process + $bits = explode(' ', $string); // bits to process $caught = array(); - $caught['color'] = false; - $caught['image'] = false; - $caught['repeat'] = false; + $caught['color'] = false; + $caught['image'] = false; + $caught['repeat'] = false; $caught['attachment'] = false; $caught['position'] = false; $i = 0; // number of catches - $none = false; foreach ($bits as $bit) { - if ($bit === '') continue; + if ($bit === '') { + continue; + } foreach ($caught as $key => $status) { if ($key != 'position') { - if ($status !== false) continue; + if ($status !== false) { + continue; + } $r = $this->info['background-' . $key]->validate($bit, $config, $context); } else { $r = $bit; } - if ($r === false) continue; + if ($r === false) { + continue; + } if ($key == 'position') { - if ($caught[$key] === false) $caught[$key] = ''; + if ($caught[$key] === false) { + $caught[$key] = ''; + } $caught[$key] .= $r . ' '; } else { $caught[$key] = $r; @@ -65,7 +85,9 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef } } - if (!$i) return false; + if (!$i) { + return false; + } if ($caught['position'] !== false) { $caught['position'] = $this->info['background-position']-> validate($caught['position'], $config, $context); @@ -73,15 +95,17 @@ class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef $ret = array(); foreach ($caught as $value) { - if ($value === false) continue; + if ($value === false) { + continue; + } $ret[] = $value; } - if (empty($ret)) return false; + if (empty($ret)) { + return false; + } return implode(' ', $ret); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php similarity index 73% rename from library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php index fae82eaec8..4580ef5a91 100644 --- a/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php @@ -44,15 +44,30 @@ class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef { + /** + * @type HTMLPurifier_AttrDef_CSS_Length + */ protected $length; + + /** + * @type HTMLPurifier_AttrDef_CSS_Percentage + */ protected $percentage; - public function __construct() { - $this->length = new HTMLPurifier_AttrDef_CSS_Length(); + public function __construct() + { + $this->length = new HTMLPurifier_AttrDef_CSS_Length(); $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage(); } - public function validate($string, $config, $context) { + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { $string = $this->parseCDATA($string); $bits = explode(' ', $string); @@ -74,7 +89,9 @@ class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef ); foreach ($bits as $bit) { - if ($bit === '') continue; + if ($bit === '') { + continue; + } // test for keyword $lbit = ctype_lower($bit) ? $bit : strtolower($bit); @@ -104,30 +121,37 @@ class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef $measures[] = $r; $i++; } - } - if (!$i) return false; // no valid values were caught + if (!$i) { + return false; + } // no valid values were caught $ret = array(); // first keyword - if ($keywords['h']) $ret[] = $keywords['h']; - elseif ($keywords['ch']) { + if ($keywords['h']) { + $ret[] = $keywords['h']; + } elseif ($keywords['ch']) { $ret[] = $keywords['ch']; $keywords['cv'] = false; // prevent re-use: center = center center + } elseif (count($measures)) { + $ret[] = array_shift($measures); } - elseif (count($measures)) $ret[] = array_shift($measures); - if ($keywords['v']) $ret[] = $keywords['v']; - elseif ($keywords['cv']) $ret[] = $keywords['cv']; - elseif (count($measures)) $ret[] = array_shift($measures); + if ($keywords['v']) { + $ret[] = $keywords['v']; + } elseif ($keywords['cv']) { + $ret[] = $keywords['cv']; + } elseif (count($measures)) { + $ret[] = array_shift($measures); + } - if (empty($ret)) return false; + if (empty($ret)) { + return false; + } return implode(' ', $ret); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Border.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php similarity index 71% rename from library/HTMLPurifier/AttrDef/CSS/Border.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php index 42a1d1b4ae..16243ba1ed 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Border.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php @@ -8,17 +8,29 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef /** * Local copy of properties this property is shorthand for. + * @type HTMLPurifier_AttrDef[] */ protected $info = array(); - public function __construct($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { $def = $config->getCSSDefinition(); $this->info['border-width'] = $def->info['border-width']; $this->info['border-style'] = $def->info['border-style']; $this->info['border-top-color'] = $def->info['border-top-color']; } - public function validate($string, $config, $context) { + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { $string = $this->parseCDATA($string); $string = $this->mungeRgb($string); $bits = explode(' ', $string); @@ -26,7 +38,9 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef $ret = ''; // return value foreach ($bits as $bit) { foreach ($this->info as $propname => $validator) { - if (isset($done[$propname])) continue; + if (isset($done[$propname])) { + continue; + } $r = $validator->validate($bit, $config, $context); if ($r !== false) { $ret .= $r . ' '; @@ -37,7 +51,6 @@ class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef } return rtrim($ret); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Color.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php similarity index 54% rename from library/HTMLPurifier/AttrDef/CSS/Color.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php index 07f95a6719..16d2a6b98c 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Color.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php @@ -6,29 +6,47 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef { - public function validate($color, $config, $context) { - + /** + * @param string $color + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($color, $config, $context) + { static $colors = null; - if ($colors === null) $colors = $config->get('Core.ColorKeywords'); + if ($colors === null) { + $colors = $config->get('Core.ColorKeywords'); + } $color = trim($color); - if ($color === '') return false; + if ($color === '') { + return false; + } $lower = strtolower($color); - if (isset($colors[$lower])) return $colors[$lower]; + if (isset($colors[$lower])) { + return $colors[$lower]; + } if (strpos($color, 'rgb(') !== false) { // rgb literal handling $length = strlen($color); - if (strpos($color, ')') !== $length - 1) return false; + if (strpos($color, ')') !== $length - 1) { + return false; + } $triad = substr($color, 4, $length - 4 - 1); $parts = explode(',', $triad); - if (count($parts) !== 3) return false; + if (count($parts) !== 3) { + return false; + } $type = false; // to ensure that they're all the same type $new_parts = array(); foreach ($parts as $part) { $part = trim($part); - if ($part === '') return false; + if ($part === '') { + return false; + } $length = strlen($part); if ($part[$length - 1] === '%') { // handle percents @@ -37,9 +55,13 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef } elseif ($type !== 'percentage') { return false; } - $num = (float) substr($part, 0, $length - 1); - if ($num < 0) $num = 0; - if ($num > 100) $num = 100; + $num = (float)substr($part, 0, $length - 1); + if ($num < 0) { + $num = 0; + } + if ($num > 100) { + $num = 100; + } $new_parts[] = "$num%"; } else { // handle integers @@ -48,10 +70,14 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef } elseif ($type !== 'integer') { return false; } - $num = (int) $part; - if ($num < 0) $num = 0; - if ($num > 255) $num = 255; - $new_parts[] = (string) $num; + $num = (int)$part; + if ($num < 0) { + $num = 0; + } + if ($num > 255) { + $num = 255; + } + $new_parts[] = (string)$num; } } $new_triad = implode(',', $new_parts); @@ -65,14 +91,15 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef $color = '#' . $color; } $length = strlen($hex); - if ($length !== 3 && $length !== 6) return false; - if (!ctype_xdigit($hex)) return false; + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } } - return $color; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Composite.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php similarity index 61% rename from library/HTMLPurifier/AttrDef/CSS/Composite.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php index de1289cba8..9c1750554f 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Composite.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php @@ -13,26 +13,36 @@ class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef { /** - * List of HTMLPurifier_AttrDef objects that may process strings + * List of objects that may process strings. + * @type HTMLPurifier_AttrDef[] * @todo Make protected */ public $defs; /** - * @param $defs List of HTMLPurifier_AttrDef objects + * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects */ - public function __construct($defs) { + public function __construct($defs) + { $this->defs = $defs; } - public function validate($string, $config, $context) { + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { foreach ($this->defs as $i => $def) { $result = $this->defs[$i]->validate($string, $config, $context); - if ($result !== false) return $result; + if ($result !== false) { + return $result; + } } return false; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php new file mode 100644 index 0000000000..9d77cc9aaf --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php @@ -0,0 +1,44 @@ +<?php + +/** + * Decorator which enables CSS properties to be disabled for specific elements. + */ +class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef +{ + /** + * @type HTMLPurifier_AttrDef + */ + public $def; + /** + * @type string + */ + public $element; + + /** + * @param HTMLPurifier_AttrDef $def Definition to wrap + * @param string $element Element to deny + */ + public function __construct($def, $element) + { + $this->def = $def; + $this->element = $element; + } + + /** + * Checks if CurrentToken is set and equal to $this->element + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $token = $context->get('CurrentToken', true); + if ($token && $token->name == $this->element) { + return false; + } + return $this->def->validate($string, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Filter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php similarity index 62% rename from library/HTMLPurifier/AttrDef/CSS/Filter.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php index 147894b861..bde4c3301f 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Filter.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php @@ -7,23 +7,37 @@ */ class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef { - + /** + * @type HTMLPurifier_AttrDef_Integer + */ protected $intValidator; - public function __construct() { + public function __construct() + { $this->intValidator = new HTMLPurifier_AttrDef_Integer(); } - public function validate($value, $config, $context) { + /** + * @param string $value + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($value, $config, $context) + { $value = $this->parseCDATA($value); - if ($value === 'none') return $value; + if ($value === 'none') { + return $value; + } // if we looped this we could support multiple filters $function_length = strcspn($value, '('); $function = trim(substr($value, 0, $function_length)); if ($function !== 'alpha' && $function !== 'Alpha' && $function !== 'progid:DXImageTransform.Microsoft.Alpha' - ) return false; + ) { + return false; + } $cursor = $function_length + 1; $parameters_length = strcspn($value, ')', $cursor); $parameters = substr($value, $cursor, $parameters_length); @@ -32,15 +46,25 @@ class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef $lookup = array(); foreach ($params as $param) { list($key, $value) = explode('=', $param); - $key = trim($key); + $key = trim($key); $value = trim($value); - if (isset($lookup[$key])) continue; - if ($key !== 'opacity') continue; + if (isset($lookup[$key])) { + continue; + } + if ($key !== 'opacity') { + continue; + } $value = $this->intValidator->validate($value, $config, $context); - if ($value === false) continue; - $int = (int) $value; - if ($int > 100) $value = '100'; - if ($int < 0) $value = '0'; + if ($value === false) { + continue; + } + $int = (int)$value; + if ($int > 100) { + $value = '100'; + } + if ($int < 0) { + $value = '0'; + } $ret_params[] = "$key=$value"; $lookup[$key] = true; } @@ -48,7 +72,6 @@ class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef $ret_function = "$function($ret_parameters)"; return $ret_function; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Font.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php similarity index 67% rename from library/HTMLPurifier/AttrDef/CSS/Font.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php index 699ee0b701..579b97ef1c 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Font.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php @@ -7,8 +7,8 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef { /** - * Local copy of component validators. - * + * Local copy of validators + * @type HTMLPurifier_AttrDef[] * @note If we moved specific CSS property definitions to their own * classes instead of having them be assembled at run time by * CSSDefinition, this wouldn't be necessary. We'd instantiate @@ -16,18 +16,28 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef */ protected $info = array(); - public function __construct($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { $def = $config->getCSSDefinition(); - $this->info['font-style'] = $def->info['font-style']; + $this->info['font-style'] = $def->info['font-style']; $this->info['font-variant'] = $def->info['font-variant']; - $this->info['font-weight'] = $def->info['font-weight']; - $this->info['font-size'] = $def->info['font-size']; - $this->info['line-height'] = $def->info['line-height']; - $this->info['font-family'] = $def->info['font-family']; + $this->info['font-weight'] = $def->info['font-weight']; + $this->info['font-size'] = $def->info['font-size']; + $this->info['line-height'] = $def->info['line-height']; + $this->info['font-family'] = $def->info['font-family']; } - public function validate($string, $config, $context) { - + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { static $system_fonts = array( 'caption' => true, 'icon' => true, @@ -39,7 +49,9 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef // regular pre-processing $string = $this->parseCDATA($string); - if ($string === '') return false; + if ($string === '') { + return false; + } // check if it's one of the keywords $lowercase_string = strtolower($string); @@ -54,15 +66,20 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef $final = ''; // output for ($i = 0, $size = count($bits); $i < $size; $i++) { - if ($bits[$i] === '') continue; + if ($bits[$i] === '') { + continue; + } switch ($stage) { - - // attempting to catch font-style, font-variant or font-weight - case 0: + case 0: // attempting to catch font-style, font-variant or font-weight foreach ($stage_1 as $validator_name) { - if (isset($caught[$validator_name])) continue; + if (isset($caught[$validator_name])) { + continue; + } $r = $this->info[$validator_name]->validate( - $bits[$i], $config, $context); + $bits[$i], + $config, + $context + ); if ($r !== false) { $final .= $r . ' '; $caught[$validator_name] = true; @@ -70,15 +87,17 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef } } // all three caught, continue on - if (count($caught) >= 3) $stage = 1; - if ($r !== false) break; - - // attempting to catch font-size and perhaps line-height - case 1: + if (count($caught) >= 3) { + $stage = 1; + } + if ($r !== false) { + break; + } + case 1: // attempting to catch font-size and perhaps line-height $found_slash = false; if (strpos($bits[$i], '/') !== false) { list($font_size, $line_height) = - explode('/', $bits[$i]); + explode('/', $bits[$i]); if ($line_height === '') { // ooh, there's a space after the slash! $line_height = false; @@ -89,14 +108,19 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef $line_height = false; } $r = $this->info['font-size']->validate( - $font_size, $config, $context); + $font_size, + $config, + $context + ); if ($r !== false) { $final .= $r; // attempt to catch line-height if ($line_height === false) { // we need to scroll forward for ($j = $i + 1; $j < $size; $j++) { - if ($bits[$j] === '') continue; + if ($bits[$j] === '') { + continue; + } if ($bits[$j] === '/') { if ($found_slash) { return false; @@ -116,7 +140,10 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef if ($found_slash) { $i = $j; $r = $this->info['line-height']->validate( - $line_height, $config, $context); + $line_height, + $config, + $context + ); if ($r !== false) { $final .= '/' . $r; } @@ -126,13 +153,14 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef break; } return false; - - // attempting to catch font-family - case 2: + case 2: // attempting to catch font-family $font_family = implode(' ', array_slice($bits, $i, $size - $i)); $r = $this->info['font-family']->validate( - $font_family, $config, $context); + $font_family, + $config, + $context + ); if ($r !== false) { $final .= $r . ' '; // processing completed successfully @@ -143,7 +171,6 @@ class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef } return false; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php new file mode 100644 index 0000000000..74e24c8816 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php @@ -0,0 +1,219 @@ +<?php + +/** + * Validates a font family list according to CSS spec + */ +class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef +{ + + protected $mask = null; + + public function __construct() + { + $this->mask = '_- '; + for ($c = 'a'; $c <= 'z'; $c++) { + $this->mask .= $c; + } + for ($c = 'A'; $c <= 'Z'; $c++) { + $this->mask .= $c; + } + for ($c = '0'; $c <= '9'; $c++) { + $this->mask .= $c; + } // cast-y, but should be fine + // special bytes used by UTF-8 + for ($i = 0x80; $i <= 0xFF; $i++) { + // We don't bother excluding invalid bytes in this range, + // because the our restriction of well-formed UTF-8 will + // prevent these from ever occurring. + $this->mask .= chr($i); + } + + /* + PHP's internal strcspn implementation is + O(length of string * length of mask), making it inefficient + for large masks. However, it's still faster than + preg_match 8) + for (p = s1;;) { + spanp = s2; + do { + if (*spanp == c || p == s1_end) { + return p - s1; + } + } while (spanp++ < (s2_end - 1)); + c = *++p; + } + */ + // possible optimization: invert the mask. + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $generic_names = array( + 'serif' => true, + 'sans-serif' => true, + 'monospace' => true, + 'fantasy' => true, + 'cursive' => true + ); + $allowed_fonts = $config->get('CSS.AllowedFonts'); + + // assume that no font names contain commas in them + $fonts = explode(',', $string); + $final = ''; + foreach ($fonts as $font) { + $font = trim($font); + if ($font === '') { + continue; + } + // match a generic name + if (isset($generic_names[$font])) { + if ($allowed_fonts === null || isset($allowed_fonts[$font])) { + $final .= $font . ', '; + } + continue; + } + // match a quoted name + if ($font[0] === '"' || $font[0] === "'") { + $length = strlen($font); + if ($length <= 2) { + continue; + } + $quote = $font[0]; + if ($font[$length - 1] !== $quote) { + continue; + } + $font = substr($font, 1, $length - 2); + } + + $font = $this->expandCSSEscape($font); + + // $font is a pure representation of the font name + + if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) { + continue; + } + + if (ctype_alnum($font) && $font !== '') { + // very simple font, allow it in unharmed + $final .= $font . ', '; + continue; + } + + // bugger out on whitespace. form feed (0C) really + // shouldn't show up regardless + $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); + + // Here, there are various classes of characters which need + // to be treated differently: + // - Alphanumeric characters are essentially safe. We + // handled these above. + // - Spaces require quoting, though most parsers will do + // the right thing if there aren't any characters that + // can be misinterpreted + // - Dashes rarely occur, but they fairly unproblematic + // for parsing/rendering purposes. + // The above characters cover the majority of Western font + // names. + // - Arbitrary Unicode characters not in ASCII. Because + // most parsers give little thought to Unicode, treatment + // of these codepoints is basically uniform, even for + // punctuation-like codepoints. These characters can + // show up in non-Western pages and are supported by most + // major browsers, for example: "MS 明朝" is a + // legitimate font-name + // <http://ja.wikipedia.org/wiki/MS_明朝>. See + // the CSS3 spec for more examples: + // <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png> + // You can see live samples of these on the Internet: + // <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック> + // However, most of these fonts have ASCII equivalents: + // for example, 'MS Mincho', and it's considered + // professional to use ASCII font names instead of + // Unicode font names. Thanks Takeshi Terada for + // providing this information. + // The following characters, to my knowledge, have not been + // used to name font names. + // - Single quote. While theoretically you might find a + // font name that has a single quote in its name (serving + // as an apostrophe, e.g. Dave's Scribble), I haven't + // been able to find any actual examples of this. + // Internet Explorer's cssText translation (which I + // believe is invoked by innerHTML) normalizes any + // quoting to single quotes, and fails to escape single + // quotes. (Note that this is not IE's behavior for all + // CSS properties, just some sort of special casing for + // font-family). So a single quote *cannot* be used + // safely in the font-family context if there will be an + // innerHTML/cssText translation. Note that Firefox 3.x + // does this too. + // - Double quote. In IE, these get normalized to + // single-quotes, no matter what the encoding. (Fun + // fact, in IE8, the 'content' CSS property gained + // support, where they special cased to preserve encoded + // double quotes, but still translate unadorned double + // quotes into single quotes.) So, because their + // fixpoint behavior is identical to single quotes, they + // cannot be allowed either. Firefox 3.x displays + // single-quote style behavior. + // - Backslashes are reduced by one (so \\ -> \) every + // iteration, so they cannot be used safely. This shows + // up in IE7, IE8 and FF3 + // - Semicolons, commas and backticks are handled properly. + // - The rest of the ASCII punctuation is handled properly. + // We haven't checked what browsers do to unadorned + // versions, but this is not important as long as the + // browser doesn't /remove/ surrounding quotes (as IE does + // for HTML). + // + // With these results in hand, we conclude that there are + // various levels of safety: + // - Paranoid: alphanumeric, spaces and dashes(?) + // - International: Paranoid + non-ASCII Unicode + // - Edgy: Everything except quotes, backslashes + // - NoJS: Standards compliance, e.g. sod IE. Note that + // with some judicious character escaping (since certain + // types of escaping doesn't work) this is theoretically + // OK as long as innerHTML/cssText is not called. + // We believe that international is a reasonable default + // (that we will implement now), and once we do more + // extensive research, we may feel comfortable with dropping + // it down to edgy. + + // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of + // str(c)spn assumes that the string was already well formed + // Unicode (which of course it is). + if (strspn($font, $this->mask) !== strlen($font)) { + continue; + } + + // Historical: + // In the absence of innerHTML/cssText, these ugly + // transforms don't pose a security risk (as \\ and \" + // might--these escapes are not supported by most browsers). + // We could try to be clever and use single-quote wrapping + // when there is a double quote present, but I have choosen + // not to implement that. (NOTE: you can reduce the amount + // of escapes by one depending on what quoting style you use) + // $font = str_replace('\\', '\\5C ', $font); + // $font = str_replace('"', '\\22 ', $font); + // $font = str_replace("'", '\\27 ', $font); + + // font possibly with spaces, requires quoting + $final .= "'$font', "; + } + $final = rtrim($final, ', '); + if ($final === '') { + return false; + } + return $final; + } + +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php new file mode 100644 index 0000000000..973002c17f --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php @@ -0,0 +1,32 @@ +<?php + +/** + * Validates based on {ident} CSS grammar production + */ +class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + + // early abort: '' and '0' (strings that convert to false) are invalid + if (!$string) { + return false; + } + + $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/'; + if (!preg_match($pattern, $string)) { + return false; + } + return $string; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php similarity index 62% rename from library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php index 4e6b35e5a0..ffc989fe80 100644 --- a/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php @@ -5,20 +5,34 @@ */ class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef { - public $def, $allow; + /** + * @type HTMLPurifier_AttrDef + */ + public $def; + /** + * @type bool + */ + public $allow; /** - * @param $def Definition to wrap - * @param $allow Whether or not to allow !important + * @param HTMLPurifier_AttrDef $def Definition to wrap + * @param bool $allow Whether or not to allow !important */ - public function __construct($def, $allow = false) { + public function __construct($def, $allow = false) + { $this->def = $def; $this->allow = $allow; } + /** * Intercepts and removes !important if necessary + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string */ - public function validate($string, $config, $context) { + public function validate($string, $config, $context) + { // test for ! and important tokens $string = trim($string); $is_important = false; @@ -32,7 +46,9 @@ class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef } } $string = $this->def->validate($string, $config, $context); - if ($this->allow && $is_important) $string .= ' !important'; + if ($this->allow && $is_important) { + $string .= ' !important'; + } return $string; } } diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php new file mode 100644 index 0000000000..f12453a04a --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php @@ -0,0 +1,77 @@ +<?php + +/** + * Represents a Length as defined by CSS. + */ +class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef +{ + + /** + * @type HTMLPurifier_Length|string + */ + protected $min; + + /** + * @type HTMLPurifier_Length|string + */ + protected $max; + + /** + * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable. + * @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable. + */ + public function __construct($min = null, $max = null) + { + $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null; + $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + // Optimizations + if ($string === '') { + return false; + } + if ($string === '0') { + return '0'; + } + if (strlen($string) === 1) { + return false; + } + + $length = HTMLPurifier_Length::make($string); + if (!$length->isValid()) { + return false; + } + + if ($this->min) { + $c = $length->compareTo($this->min); + if ($c === false) { + return false; + } + if ($c < 0) { + return false; + } + } + if ($this->max) { + $c = $length->compareTo($this->max); + if ($c === false) { + return false; + } + if ($c > 0) { + return false; + } + } + return $length->toString(); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php new file mode 100644 index 0000000000..e74d42654e --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php @@ -0,0 +1,112 @@ +<?php + +/** + * Validates shorthand CSS property list-style. + * @warning Does not support url tokens that have internal spaces. + */ +class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef +{ + + /** + * Local copy of validators. + * @type HTMLPurifier_AttrDef[] + * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl. + */ + protected $info; + + /** + * @param HTMLPurifier_Config $config + */ + public function __construct($config) + { + $def = $config->getCSSDefinition(); + $this->info['list-style-type'] = $def->info['list-style-type']; + $this->info['list-style-position'] = $def->info['list-style-position']; + $this->info['list-style-image'] = $def->info['list-style-image']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // assumes URI doesn't have spaces in it + $bits = explode(' ', strtolower($string)); // bits to process + + $caught = array(); + $caught['type'] = false; + $caught['position'] = false; + $caught['image'] = false; + + $i = 0; // number of catches + $none = false; + + foreach ($bits as $bit) { + if ($i >= 3) { + return; + } // optimization bit + if ($bit === '') { + continue; + } + foreach ($caught as $key => $status) { + if ($status !== false) { + continue; + } + $r = $this->info['list-style-' . $key]->validate($bit, $config, $context); + if ($r === false) { + continue; + } + if ($r === 'none') { + if ($none) { + continue; + } else { + $none = true; + } + if ($key == 'image') { + continue; + } + } + $caught[$key] = $r; + $i++; + break; + } + } + + if (!$i) { + return false; + } + + $ret = array(); + + // construct type + if ($caught['type']) { + $ret[] = $caught['type']; + } + + // construct image + if ($caught['image']) { + $ret[] = $caught['image']; + } + + // construct position + if ($caught['position']) { + $ret[] = $caught['position']; + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Multiple.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php similarity index 65% rename from library/HTMLPurifier/AttrDef/CSS/Multiple.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php index 4d62a40d7f..e707f871ca 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Multiple.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php @@ -13,9 +13,9 @@ */ class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef { - /** * Instance of component definition to defer validation to. + * @type HTMLPurifier_AttrDef * @todo Make protected */ public $single; @@ -27,32 +27,45 @@ class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef public $max; /** - * @param $single HTMLPurifier_AttrDef to multiply - * @param $max Max number of values allowed (usually four) + * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply + * @param int $max Max number of values allowed (usually four) */ - public function __construct($single, $max = 4) { + public function __construct($single, $max = 4) + { $this->single = $single; $this->max = $max; } - public function validate($string, $config, $context) { - $string = $this->parseCDATA($string); - if ($string === '') return false; + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->mungeRgb($this->parseCDATA($string)); + if ($string === '') { + return false; + } $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n $length = count($parts); $final = ''; for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) { - if (ctype_space($parts[$i])) continue; + if (ctype_space($parts[$i])) { + continue; + } $result = $this->single->validate($parts[$i], $config, $context); if ($result !== false) { $final .= $result . ' '; $num++; } } - if ($final === '') return false; + if ($final === '') { + return false; + } return rtrim($final); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/Number.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php similarity index 55% rename from library/HTMLPurifier/AttrDef/CSS/Number.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php index 3f99e12ec2..8edc159e72 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Number.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php @@ -7,32 +7,44 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef { /** - * Bool indicating whether or not only positive values allowed. + * Indicates whether or not only positive values are allowed. + * @type bool */ protected $non_negative = false; /** - * @param $non_negative Bool indicating whether negatives are forbidden + * @param bool $non_negative indicates whether negatives are forbidden */ - public function __construct($non_negative = false) { + public function __construct($non_negative = false) + { $this->non_negative = $non_negative; } /** + * @param string $number + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string|bool * @warning Some contexts do not pass $config, $context. These * variables should not be used without checking HTMLPurifier_Length */ - public function validate($number, $config, $context) { - + public function validate($number, $config, $context) + { $number = $this->parseCDATA($number); - if ($number === '') return false; - if ($number === '0') return '0'; + if ($number === '') { + return false; + } + if ($number === '0') { + return '0'; + } $sign = ''; switch ($number[0]) { case '-': - if ($this->non_negative) return false; + if ($this->non_negative) { + return false; + } $sign = '-'; case '+': $number = substr($number, 1); @@ -44,14 +56,20 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef } // Period is the only non-numeric character allowed - if (strpos($number, '.') === false) return false; + if (strpos($number, '.') === false) { + return false; + } list($left, $right) = explode('.', $number, 2); - if ($left === '' && $right === '') return false; - if ($left !== '' && !ctype_digit($left)) return false; + if ($left === '' && $right === '') { + return false; + } + if ($left !== '' && !ctype_digit($left)) { + return false; + } - $left = ltrim($left, '0'); + $left = ltrim($left, '0'); $right = rtrim($right, '0'); if ($right === '') { @@ -59,11 +77,8 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef } elseif (!ctype_digit($right)) { return false; } - return $sign . $left . '.' . $right; - } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php new file mode 100644 index 0000000000..f0f25c50a8 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php @@ -0,0 +1,54 @@ +<?php + +/** + * Validates a Percentage as defined by the CSS spec. + */ +class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef +{ + + /** + * Instance to defer number validation to. + * @type HTMLPurifier_AttrDef_CSS_Number + */ + protected $number_def; + + /** + * @param bool $non_negative Whether to forbid negative values + */ + public function __construct($non_negative = false) + { + $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + if ($string === '') { + return false; + } + $length = strlen($string); + if ($length === 1) { + return false; + } + if ($string[$length - 1] !== '%') { + return false; + } + + $number = substr($string, 0, $length - 1); + $number = $this->number_def->validate($number, $config, $context); + + if ($number === false) { + return false; + } + return "$number%"; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php similarity index 69% rename from library/HTMLPurifier/AttrDef/CSS/TextDecoration.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php index 772c922d80..5fd4b7f7b4 100644 --- a/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php @@ -8,8 +8,14 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef { - public function validate($string, $config, $context) { - + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { static $allowed_values = array( 'line-through' => true, 'overline' => true, @@ -18,7 +24,9 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef $string = strtolower($this->parseCDATA($string)); - if ($string === 'none') return $string; + if ($string === 'none') { + return $string; + } $parts = explode(' ', $string); $final = ''; @@ -28,11 +36,11 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef } } $final = rtrim($final); - if ($final === '') return false; + if ($final === '') { + return false; + } return $final; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/CSS/URI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php similarity index 58% rename from library/HTMLPurifier/AttrDef/CSS/URI.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php index 1df17dc25b..f9434230e2 100644 --- a/library/HTMLPurifier/AttrDef/CSS/URI.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php @@ -12,25 +12,39 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI { - public function __construct() { + public function __construct() + { parent::__construct(true); // always embedded } - public function validate($uri_string, $config, $context) { + /** + * @param string $uri_string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($uri_string, $config, $context) + { // parse the URI out of the string and then pass it onto // the parent object $uri_string = $this->parseCDATA($uri_string); - if (strpos($uri_string, 'url(') !== 0) return false; + if (strpos($uri_string, 'url(') !== 0) { + return false; + } $uri_string = substr($uri_string, 4); $new_length = strlen($uri_string) - 1; - if ($uri_string[$new_length] != ')') return false; + if ($uri_string[$new_length] != ')') { + return false; + } $uri = trim(substr($uri_string, 0, $new_length)); if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) { $quote = $uri[0]; $new_length = strlen($uri) - 1; - if ($uri[$new_length] !== $quote) return false; + if ($uri[$new_length] !== $quote) { + return false; + } $uri = substr($uri, 1, $new_length - 1); } @@ -38,15 +52,23 @@ class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI $result = parent::validate($uri, $config, $context); - if ($result === false) return false; + if ($result === false) { + return false; + } // extra sanity check; should have been done by URI $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result); + // suspicious characters are ()'; we're going to percent encode + // them for safety. + $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result); + + // there's an extra bug where ampersands lose their escaping on + // an innerHTML cycle, so a very unlucky query parameter could + // then change the meaning of the URL. Unfortunately, there's + // not much we can do about that... return "url(\"$result\")"; - } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php new file mode 100644 index 0000000000..6698a00c01 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php @@ -0,0 +1,44 @@ +<?php + +/** + * Dummy AttrDef that mimics another AttrDef, BUT it generates clones + * with make. + */ +class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef +{ + /** + * What we're cloning. + * @type HTMLPurifier_AttrDef + */ + protected $clone; + + /** + * @param HTMLPurifier_AttrDef $clone + */ + public function __construct($clone) + { + $this->clone = $clone; + } + + /** + * @param string $v + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($v, $config, $context) + { + return $this->clone->validate($v, $config, $context); + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + return clone $this->clone; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/Enum.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php similarity index 70% rename from library/HTMLPurifier/AttrDef/Enum.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php index 5d603ebcc6..8abda7f6e2 100644 --- a/library/HTMLPurifier/AttrDef/Enum.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php @@ -12,9 +12,10 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef /** * Lookup table of valid values. + * @type array * @todo Make protected */ - public $valid_values = array(); + public $valid_values = array(); /** * Bool indicating whether or not enumeration is case sensitive. @@ -23,17 +24,23 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef protected $case_sensitive = false; // values according to W3C spec /** - * @param $valid_values List of valid values - * @param $case_sensitive Bool indicating whether or not case sensitive + * @param array $valid_values List of valid values + * @param bool $case_sensitive Whether or not case sensitive */ - public function __construct( - $valid_values = array(), $case_sensitive = false - ) { + public function __construct($valid_values = array(), $case_sensitive = false) + { $this->valid_values = array_flip($valid_values); $this->case_sensitive = $case_sensitive; } - public function validate($string, $config, $context) { + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { $string = trim($string); if (!$this->case_sensitive) { // we may want to do full case-insensitive libraries @@ -45,11 +52,13 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef } /** - * @param $string In form of comma-delimited list of case-insensitive + * @param string $string In form of comma-delimited list of case-insensitive * valid values. Example: "foo,bar,baz". Prepend "s:" to make * case sensitive + * @return HTMLPurifier_AttrDef_Enum */ - public function make($string) { + public function make($string) + { if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { $string = substr($string, 2); $sensitive = true; @@ -59,7 +68,6 @@ class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef $values = explode(',', $string); return new HTMLPurifier_AttrDef_Enum($values, $sensitive); } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php new file mode 100644 index 0000000000..dea15d2cd2 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php @@ -0,0 +1,48 @@ +<?php + +/** + * Validates a boolean attribute + */ +class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef +{ + + /** + * @type bool + */ + protected $name; + + /** + * @type bool + */ + public $minimized = true; + + /** + * @param bool $name + */ + public function __construct($name = false) + { + $this->name = $name; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + return $this->name; + } + + /** + * @param string $string Name of attribute + * @return HTMLPurifier_AttrDef_HTML_Bool + */ + public function make($string) + { + return new HTMLPurifier_AttrDef_HTML_Bool($string); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/Class.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php similarity index 66% rename from library/HTMLPurifier/AttrDef/HTML/Class.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php index 370068d975..d5013488fc 100644 --- a/library/HTMLPurifier/AttrDef/HTML/Class.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php @@ -5,7 +5,14 @@ */ class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens { - protected function split($string, $config, $context) { + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + protected function split($string, $config, $context) + { // really, this twiddle should be lazy loaded $name = $config->getDefinition('HTML')->doctype->name; if ($name == "XHTML 1.1" || $name == "XHTML 2.0") { @@ -14,13 +21,20 @@ class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens return preg_split('/\s+/', $string); } } - protected function filter($tokens, $config, $context) { + + /** + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function filter($tokens, $config, $context) + { $allowed = $config->get('Attr.AllowedClasses'); $forbidden = $config->get('Attr.ForbiddenClasses'); $ret = array(); foreach ($tokens as $token) { - if ( - ($allowed === null || isset($allowed[$token])) && + if (($allowed === null || isset($allowed[$token])) && !isset($forbidden[$token]) && // We need this O(n) check because of PHP's array // implementation that casts -0 to 0. diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php new file mode 100644 index 0000000000..946ebb7820 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php @@ -0,0 +1,51 @@ +<?php + +/** + * Validates a color according to the HTML spec. + */ +class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $colors = null; + if ($colors === null) { + $colors = $config->get('Core.ColorKeywords'); + } + + $string = trim($string); + + if (empty($string)) { + return false; + } + $lower = strtolower($string); + if (isset($colors[$lower])) { + return $colors[$lower]; + } + if ($string[0] === '#') { + $hex = substr($string, 1); + } else { + $hex = $string; + } + + $length = strlen($hex); + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } + if ($length === 3) { + $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; + } + return "#$hex"; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php new file mode 100644 index 0000000000..d79ba12b3f --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php @@ -0,0 +1,38 @@ +<?php + +/** + * Special-case enum attribute definition that lazy loads allowed frame targets + */ +class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum +{ + + /** + * @type array + */ + public $valid_values = false; // uninitialized value + + /** + * @type bool + */ + protected $case_sensitive = false; + + public function __construct() + { + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + if ($this->valid_values === false) { + $this->valid_values = $config->get('Attr.AllowedFrameTargets'); + } + return parent::validate($string, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php new file mode 100644 index 0000000000..3d86efb44c --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php @@ -0,0 +1,105 @@ +<?php + +/** + * Validates the HTML attribute ID. + * @warning Even though this is the id processor, it + * will ignore the directive Attr:IDBlacklist, since it will only + * go according to the ID accumulator. Since the accumulator is + * automatically generated, it will have already absorbed the + * blacklist. If you're hacking around, make sure you use load()! + */ + +class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef +{ + + // selector is NOT a valid thing to use for IDREFs, because IDREFs + // *must* target IDs that exist, whereas selector #ids do not. + + /** + * Determines whether or not we're validating an ID in a CSS + * selector context. + * @type bool + */ + protected $selector; + + /** + * @param bool $selector + */ + public function __construct($selector = false) + { + $this->selector = $selector; + } + + /** + * @param string $id + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($id, $config, $context) + { + if (!$this->selector && !$config->get('Attr.EnableID')) { + return false; + } + + $id = trim($id); // trim it first + + if ($id === '') { + return false; + } + + $prefix = $config->get('Attr.IDPrefix'); + if ($prefix !== '') { + $prefix .= $config->get('Attr.IDPrefixLocal'); + // prevent re-appending the prefix + if (strpos($id, $prefix) !== 0) { + $id = $prefix . $id; + } + } elseif ($config->get('Attr.IDPrefixLocal') !== '') { + trigger_error( + '%Attr.IDPrefixLocal cannot be used unless ' . + '%Attr.IDPrefix is set', + E_USER_WARNING + ); + } + + if (!$this->selector) { + $id_accumulator =& $context->get('IDAccumulator'); + if (isset($id_accumulator->ids[$id])) { + return false; + } + } + + // we purposely avoid using regex, hopefully this is faster + + if (ctype_alpha($id)) { + $result = true; + } else { + if (!ctype_alpha(@$id[0])) { + return false; + } + // primitive style of regexps, I suppose + $trim = trim( + $id, + 'A..Za..z0..9:-._' + ); + $result = ($trim === ''); + } + + $regexp = $config->get('Attr.IDBlacklistRegexp'); + if ($regexp && preg_match($regexp, $id)) { + return false; + } + + if (!$this->selector && $result) { + $id_accumulator->add($id); + } + + // if no change was made to the ID, return the result + // else, return the new id if stripping whitespace made it + // valid, or return false. + return $result ? $id : false; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php new file mode 100644 index 0000000000..1c4006fbbd --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php @@ -0,0 +1,56 @@ +<?php + +/** + * Validates the HTML type length (not to be confused with CSS's length). + * + * This accepts integer pixels or percentages as lengths for certain + * HTML attributes. + */ + +class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '') { + return false; + } + + $parent_result = parent::validate($string, $config, $context); + if ($parent_result !== false) { + return $parent_result; + } + + $length = strlen($string); + $last_char = $string[$length - 1]; + + if ($last_char !== '%') { + return false; + } + + $points = substr($string, 0, $length - 1); + + if (!is_numeric($points)) { + return false; + } + + $points = (int)$points; + + if ($points < 0) { + return '0%'; + } + if ($points > 100) { + return '100%'; + } + return ((string)$points) . '%'; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php similarity index 56% rename from library/HTMLPurifier/AttrDef/HTML/LinkTypes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php index 76d25ed088..63fa04c15c 100644 --- a/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php @@ -9,26 +9,44 @@ class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef { - /** Name config attribute to pull. */ + /** + * Name config attribute to pull. + * @type string + */ protected $name; - public function __construct($name) { + /** + * @param string $name + */ + public function __construct($name) + { $configLookup = array( 'rel' => 'AllowedRel', 'rev' => 'AllowedRev' ); if (!isset($configLookup[$name])) { - trigger_error('Unrecognized attribute name for link '. - 'relationship.', E_USER_ERROR); + trigger_error( + 'Unrecognized attribute name for link ' . + 'relationship.', + E_USER_ERROR + ); return; } $this->name = $configLookup[$name]; } - public function validate($string, $config, $context) { - + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { $allowed = $config->get('Attr.' . $this->name); - if (empty($allowed)) return false; + if (empty($allowed)) { + return false; + } $string = $this->parseCDATA($string); $parts = explode(' ', $string); @@ -37,17 +55,18 @@ class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef $ret_lookup = array(); foreach ($parts as $part) { $part = strtolower(trim($part)); - if (!isset($allowed[$part])) continue; + if (!isset($allowed[$part])) { + continue; + } $ret_lookup[$part] = true; } - if (empty($ret_lookup)) return false; + if (empty($ret_lookup)) { + return false; + } $string = implode(' ', array_keys($ret_lookup)); - return $string; - } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php new file mode 100644 index 0000000000..bbb20f2f80 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php @@ -0,0 +1,60 @@ +<?php + +/** + * Validates a MultiLength as defined by the HTML spec. + * + * A multilength is either a integer (pixel count), a percentage, or + * a relative number. + */ +class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '') { + return false; + } + + $parent_result = parent::validate($string, $config, $context); + if ($parent_result !== false) { + return $parent_result; + } + + $length = strlen($string); + $last_char = $string[$length - 1]; + + if ($last_char !== '*') { + return false; + } + + $int = substr($string, 0, $length - 1); + + if ($int == '') { + return '*'; + } + if (!is_numeric($int)) { + return false; + } + + $int = (int)$int; + if ($int < 0) { + return false; + } + if ($int == 0) { + return '0'; + } + if ($int == 1) { + return '*'; + } + return ((string)$int) . '*'; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php similarity index 55% rename from library/HTMLPurifier/AttrDef/HTML/Nmtokens.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php index aa34120bd2..f79683b4fc 100644 --- a/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Nmtokens.php @@ -6,24 +6,38 @@ class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef { - public function validate($string, $config, $context) { - + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { $string = trim($string); // early abort: '' and '0' (strings that convert to false) are invalid - if (!$string) return false; + if (!$string) { + return false; + } $tokens = $this->split($string, $config, $context); $tokens = $this->filter($tokens, $config, $context); - if (empty($tokens)) return false; + if (empty($tokens)) { + return false; + } return implode(' ', $tokens); - } /** * Splits a space separated list of tokens into its constituent parts. + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array */ - protected function split($string, $config, $context) { + protected function split($string, $config, $context) + { // OPTIMIZABLE! // do the preg_match, capture all subpatterns for reformulation @@ -31,9 +45,9 @@ class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef // escaping because I don't know how to do that with regexps // and plus it would complicate optimization efforts (you never // see that anyway). - $pattern = '/(?:(?<=\s)|\A)'. // look behind for space or string start - '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)'. - '(?:(?=\s)|\z)/'; // look ahead for space or string end + $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start + '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' . + '(?:(?=\s)|\z)/'; // look ahead for space or string end preg_match_all($pattern, $string, $matches); return $matches[1]; } @@ -42,11 +56,15 @@ class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef * Template method for removing certain tokens based on arbitrary criteria. * @note If we wanted to be really functional, we'd do an array_filter * with a callback. But... we're not. + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array */ - protected function filter($tokens, $config, $context) { + protected function filter($tokens, $config, $context) + { return $tokens; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php new file mode 100644 index 0000000000..a1d019e095 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php @@ -0,0 +1,76 @@ +<?php + +/** + * Validates an integer representation of pixels according to the HTML spec. + */ +class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef +{ + + /** + * @type int + */ + protected $max; + + /** + * @param int $max + */ + public function __construct($max = null) + { + $this->max = $max; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '0') { + return $string; + } + if ($string === '') { + return false; + } + $length = strlen($string); + if (substr($string, $length - 2) == 'px') { + $string = substr($string, 0, $length - 2); + } + if (!is_numeric($string)) { + return false; + } + $int = (int)$string; + + if ($int < 0) { + return '0'; + } + + // upper-bound value, extremely high values can + // crash operating systems, see <http://ha.ckers.org/imagecrash.html> + // WARNING, above link WILL crash you if you're using Windows + + if ($this->max !== null && $int > $this->max) { + return (string)$this->max; + } + return (string)$int; + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + if ($string === '') { + $max = null; + } else { + $max = (int)$string; + } + $class = get_class($this); + return new $class($max); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/Integer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php similarity index 55% rename from library/HTMLPurifier/AttrDef/Integer.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php index d59738d2a2..400e707d2f 100644 --- a/library/HTMLPurifier/AttrDef/Integer.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php @@ -11,17 +11,20 @@ class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef { /** - * Bool indicating whether or not negative values are allowed + * Whether or not negative values are allowed. + * @type bool */ protected $negative = true; /** - * Bool indicating whether or not zero is allowed + * Whether or not zero is allowed. + * @type bool */ protected $zero = true; /** - * Bool indicating whether or not positive values are allowed + * Whether or not positive values are allowed. + * @type bool */ protected $positive = true; @@ -30,44 +33,59 @@ class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef * @param $zero Bool indicating whether or not zero is allowed * @param $positive Bool indicating whether or not positive values are allowed */ - public function __construct( - $negative = true, $zero = true, $positive = true - ) { + public function __construct($negative = true, $zero = true, $positive = true) + { $this->negative = $negative; - $this->zero = $zero; + $this->zero = $zero; $this->positive = $positive; } - public function validate($integer, $config, $context) { - + /** + * @param string $integer + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($integer, $config, $context) + { $integer = $this->parseCDATA($integer); - if ($integer === '') return false; + if ($integer === '') { + return false; + } // we could possibly simply typecast it to integer, but there are // certain fringe cases that must not return an integer. // clip leading sign - if ( $this->negative && $integer[0] === '-' ) { + if ($this->negative && $integer[0] === '-') { $digits = substr($integer, 1); - if ($digits === '0') $integer = '0'; // rm minus sign for zero - } elseif( $this->positive && $integer[0] === '+' ) { + if ($digits === '0') { + $integer = '0'; + } // rm minus sign for zero + } elseif ($this->positive && $integer[0] === '+') { $digits = $integer = substr($integer, 1); // rm unnecessary plus } else { $digits = $integer; } // test if it's numeric - if (!ctype_digit($digits)) return false; + if (!ctype_digit($digits)) { + return false; + } // perform scope tests - if (!$this->zero && $integer == 0) return false; - if (!$this->positive && $integer > 0) return false; - if (!$this->negative && $integer < 0) return false; + if (!$this->zero && $integer == 0) { + return false; + } + if (!$this->positive && $integer > 0) { + return false; + } + if (!$this->negative && $integer < 0) { + return false; + } return $integer; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/Lang.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php similarity index 67% rename from library/HTMLPurifier/AttrDef/Lang.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php index 10e6da56db..2a55cea642 100644 --- a/library/HTMLPurifier/AttrDef/Lang.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php @@ -7,15 +7,25 @@ class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef { - public function validate($string, $config, $context) { - + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { $string = trim($string); - if (!$string) return false; + if (!$string) { + return false; + } $subtags = explode('-', $string); $num_subtags = count($subtags); - if ($num_subtags == 0) return false; // sanity check + if ($num_subtags == 0) { // sanity check + return false; + } // process primary subtag : $subtags[0] $length = strlen($subtags[0]); @@ -23,15 +33,15 @@ class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef case 0: return false; case 1: - if (! ($subtags[0] == 'x' || $subtags[0] == 'i') ) { + if (!($subtags[0] == 'x' || $subtags[0] == 'i')) { return false; } break; case 2: case 3: - if (! ctype_alpha($subtags[0]) ) { + if (!ctype_alpha($subtags[0])) { return false; - } elseif (! ctype_lower($subtags[0]) ) { + } elseif (!ctype_lower($subtags[0])) { $subtags[0] = strtolower($subtags[0]); } break; @@ -40,17 +50,23 @@ class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef } $new_string = $subtags[0]; - if ($num_subtags == 1) return $new_string; + if ($num_subtags == 1) { + return $new_string; + } // process second subtag : $subtags[1] $length = strlen($subtags[1]); if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) { return $new_string; } - if (!ctype_lower($subtags[1])) $subtags[1] = strtolower($subtags[1]); + if (!ctype_lower($subtags[1])) { + $subtags[1] = strtolower($subtags[1]); + } $new_string .= '-' . $subtags[1]; - if ($num_subtags == 2) return $new_string; + if ($num_subtags == 2) { + return $new_string; + } // process all other subtags, index 2 and up for ($i = 2; $i < $num_subtags; $i++) { @@ -63,11 +79,8 @@ class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef } $new_string .= '-' . $subtags[$i]; } - return $new_string; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/Switch.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php similarity index 62% rename from library/HTMLPurifier/AttrDef/Switch.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php index c9e3ed193e..c7eb3199a4 100644 --- a/library/HTMLPurifier/AttrDef/Switch.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php @@ -6,21 +6,41 @@ class HTMLPurifier_AttrDef_Switch { + /** + * @type string + */ protected $tag; - protected $withTag, $withoutTag; + + /** + * @type HTMLPurifier_AttrDef + */ + protected $withTag; + + /** + * @type HTMLPurifier_AttrDef + */ + protected $withoutTag; /** * @param string $tag Tag name to switch upon * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token */ - public function __construct($tag, $with_tag, $without_tag) { + public function __construct($tag, $with_tag, $without_tag) + { $this->tag = $tag; $this->withTag = $with_tag; $this->withoutTag = $without_tag; } - public function validate($string, $config, $context) { + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { $token = $context->get('CurrentToken', true); if (!$token || $token->name !== $this->tag) { return $this->withoutTag->validate($string, $config, $context); @@ -28,7 +48,6 @@ class HTMLPurifier_AttrDef_Switch return $this->withTag->validate($string, $config, $context); } } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php new file mode 100644 index 0000000000..4553a4ea9b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php @@ -0,0 +1,21 @@ +<?php + +/** + * Validates arbitrary text according to the HTML spec. + */ +class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef +{ + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + return $this->parseCDATA($string); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/URI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php similarity index 53% rename from library/HTMLPurifier/AttrDef/URI.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php index 01a6d83e95..c1cd89772c 100644 --- a/library/HTMLPurifier/AttrDef/URI.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php @@ -7,31 +7,54 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef { + /** + * @type HTMLPurifier_URIParser + */ protected $parser; + + /** + * @type bool + */ protected $embedsResource; /** - * @param $embeds_resource_resource Does the URI here result in an extra HTTP request? + * @param bool $embeds_resource Does the URI here result in an extra HTTP request? */ - public function __construct($embeds_resource = false) { + public function __construct($embeds_resource = false) + { $this->parser = new HTMLPurifier_URIParser(); - $this->embedsResource = (bool) $embeds_resource; + $this->embedsResource = (bool)$embeds_resource; } - public function make($string) { - $embeds = (bool) $string; + /** + * @param string $string + * @return HTMLPurifier_AttrDef_URI + */ + public function make($string) + { + $embeds = ($string === 'embedded'); return new HTMLPurifier_AttrDef_URI($embeds); } - public function validate($uri, $config, $context) { - - if ($config->get('URI.Disable')) return false; + /** + * @param string $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($uri, $config, $context) + { + if ($config->get('URI.Disable')) { + return false; + } $uri = $this->parseCDATA($uri); // parse the URI $uri = $this->parser->parse($uri); - if ($uri === false) return false; + if ($uri === false) { + return false; + } // add embedded flag to context for validators $context->register('EmbeddedURI', $this->embedsResource); @@ -41,23 +64,35 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef // generic validation $result = $uri->validate($config, $context); - if (!$result) break; + if (!$result) { + break; + } // chained filtering $uri_def = $config->getDefinition('URI'); $result = $uri_def->filter($uri, $config, $context); - if (!$result) break; + if (!$result) { + break; + } // scheme-specific validation $scheme_obj = $uri->getSchemeObj($config, $context); - if (!$scheme_obj) break; - if ($this->embedsResource && !$scheme_obj->browsable) break; + if (!$scheme_obj) { + break; + } + if ($this->embedsResource && !$scheme_obj->browsable) { + break; + } $result = $scheme_obj->validate($uri, $config, $context); - if (!$result) break; + if (!$result) { + break; + } // Post chained filtering $result = $uri_def->postFilter($uri, $config, $context); - if (!$result) break; + if (!$result) { + break; + } // survived gauntlet $ok = true; @@ -65,13 +100,12 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef } while (false); $context->destroy('EmbeddedURI'); - if (!$ok) return false; - + if (!$ok) { + return false; + } // back to string return $uri->toString(); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/URI/Email.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php similarity index 73% rename from library/HTMLPurifier/AttrDef/URI/Email.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php index bfee9d166c..daf32b7643 100644 --- a/library/HTMLPurifier/AttrDef/URI/Email.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php @@ -5,8 +5,11 @@ abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef /** * Unpacks a mailbox into its display-name and address + * @param string $string + * @return mixed */ - function unpack($string) { + public function unpack($string) + { // needs to be implemented } diff --git a/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php similarity index 65% rename from library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php index 94c715ab43..52c0d59683 100644 --- a/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php @@ -7,15 +7,23 @@ class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email { - public function validate($string, $config, $context) { + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { // no support for named mailboxes i.e. "Bob <bob@example.com>" // that needs more percent encoding to be done - if ($string == '') return false; + if ($string == '') { + return false; + } $string = trim($string); $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string); return $result ? $string : false; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php new file mode 100644 index 0000000000..e7df800b1e --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php @@ -0,0 +1,128 @@ +<?php + +/** + * Validates a host according to the IPv4, IPv6 and DNS (future) specifications. + */ +class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef +{ + + /** + * IPv4 sub-validator. + * @type HTMLPurifier_AttrDef_URI_IPv4 + */ + protected $ipv4; + + /** + * IPv6 sub-validator. + * @type HTMLPurifier_AttrDef_URI_IPv6 + */ + protected $ipv6; + + public function __construct() + { + $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); + $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $length = strlen($string); + // empty hostname is OK; it's usually semantically equivalent: + // the default host as defined by a URI scheme is used: + // + // If the URI scheme defines a default for host, then that + // default applies when the host subcomponent is undefined + // or when the registered name is empty (zero length). + if ($string === '') { + return ''; + } + if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') { + //IPv6 + $ip = substr($string, 1, $length - 2); + $valid = $this->ipv6->validate($ip, $config, $context); + if ($valid === false) { + return false; + } + return '[' . $valid . ']'; + } + + // need to do checks on unusual encodings too + $ipv4 = $this->ipv4->validate($string, $config, $context); + if ($ipv4 !== false) { + return $ipv4; + } + + // A regular domain name. + + // This doesn't match I18N domain names, but we don't have proper IRI support, + // so force users to insert Punycode. + + // There is not a good sense in which underscores should be + // allowed, since it's technically not! (And if you go as + // far to allow everything as specified by the DNS spec... + // well, that's literally everything, modulo some space limits + // for the components and the overall name (which, by the way, + // we are NOT checking!). So we (arbitrarily) decide this: + // let's allow underscores wherever we would have allowed + // hyphens, if they are enabled. This is a pretty good match + // for browser behavior, for example, a large number of browsers + // cannot handle foo_.example.com, but foo_bar.example.com is + // fairly well supported. + $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : ''; + + // The productions describing this are: + $a = '[a-z]'; // alpha + $an = '[a-z0-9]'; // alphanum + $and = "[a-z0-9-$underscore]"; // alphanum | "-" + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + $domainlabel = "$an($and*$an)?"; + // toplabel = alpha | alpha *( alphanum | "-" ) alphanum + $toplabel = "$a($and*$an)?"; + // hostname = *( domainlabel "." ) toplabel [ "." ] + if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { + return $string; + } + + // If we have Net_IDNA2 support, we can support IRIs by + // punycoding them. (This is the most portable thing to do, + // since otherwise we have to assume browsers support + + if ($config->get('Core.EnableIDNA')) { + $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true)); + // we need to encode each period separately + $parts = explode('.', $string); + try { + $new_parts = array(); + foreach ($parts as $part) { + $encodable = false; + for ($i = 0, $c = strlen($part); $i < $c; $i++) { + if (ord($part[$i]) > 0x7a) { + $encodable = true; + break; + } + } + if (!$encodable) { + $new_parts[] = $part; + } else { + $new_parts[] = $idna->encode($part); + } + } + $string = implode('.', $new_parts); + if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { + return $string; + } + } catch (Exception $e) { + // XXX error reporting + } + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrDef/URI/IPv4.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php similarity index 51% rename from library/HTMLPurifier/AttrDef/URI/IPv4.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php index ec4cf591b8..30ac16c9e7 100644 --- a/library/HTMLPurifier/AttrDef/URI/IPv4.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php @@ -8,32 +8,38 @@ class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef { /** - * IPv4 regex, protected so that IPv6 can reuse it + * IPv4 regex, protected so that IPv6 can reuse it. + * @type string */ protected $ip4; - public function validate($aIP, $config, $context) { - - if (!$this->ip4) $this->_loadRegex(); - - if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) - { - return $aIP; + /** + * @param string $aIP + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($aIP, $config, $context) + { + if (!$this->ip4) { + $this->_loadRegex(); } + if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) { + return $aIP; + } return false; - } /** * Lazy load function to prevent regex from being stuffed in * cache. */ - protected function _loadRegex() { + protected function _loadRegex() + { $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255 $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})"; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php new file mode 100644 index 0000000000..f243793eeb --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php @@ -0,0 +1,89 @@ +<?php + +/** + * Validates an IPv6 address. + * @author Feyd @ forums.devnetwork.net (public domain) + * @note This function requires brackets to have been removed from address + * in URI. + */ +class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4 +{ + + /** + * @param string $aIP + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($aIP, $config, $context) + { + if (!$this->ip4) { + $this->_loadRegex(); + } + + $original = $aIP; + + $hex = '[0-9a-fA-F]'; + $blk = '(?:' . $hex . '{1,4})'; + $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128 + + // prefix check + if (strpos($aIP, '/') !== false) { + if (preg_match('#' . $pre . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + unset($find); + } else { + return false; + } + } + + // IPv4-compatiblity check + if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + $ip = explode('.', $find[0]); + $ip = array_map('dechex', $ip); + $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3]; + unset($find, $ip); + } + + // compression check + $aIP = explode('::', $aIP); + $c = count($aIP); + if ($c > 2) { + return false; + } elseif ($c == 2) { + list($first, $second) = $aIP; + $first = explode(':', $first); + $second = explode(':', $second); + + if (count($first) + count($second) > 8) { + return false; + } + + while (count($first) < 8) { + array_push($first, '0'); + } + + array_splice($first, 8 - count($second), 8, $second); + $aIP = $first; + unset($first, $second); + } else { + $aIP = explode(':', $aIP[0]); + } + $c = count($aIP); + + if ($c != 8) { + return false; + } + + // All the pieces should be 16-bit hex strings. Are they? + foreach ($aIP as $piece) { + if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) { + return false; + } + } + return $original; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php similarity index 63% rename from library/HTMLPurifier/AttrTransform.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php index e61d3e01b6..b428331f15 100644 --- a/library/HTMLPurifier/AttrTransform.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php @@ -20,37 +20,41 @@ abstract class HTMLPurifier_AttrTransform /** * Abstract: makes changes to the attributes dependent on multiple values. * - * @param $attr Assoc array of attributes, usually from + * @param array $attr Assoc array of attributes, usually from * HTMLPurifier_Token_Tag::$attr - * @param $config Mandatory HTMLPurifier_Config object. - * @param $context Mandatory HTMLPurifier_Context object - * @returns Processed attribute array. + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object. + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object + * @return array Processed attribute array. */ abstract public function transform($attr, $config, $context); /** * Prepends CSS properties to the style attribute, creating the * attribute if it doesn't exist. - * @param $attr Attribute array to process (passed by reference) - * @param $css CSS to prepend + * @param array &$attr Attribute array to process (passed by reference) + * @param string $css CSS to prepend */ - public function prependCSS(&$attr, $css) { + public function prependCSS(&$attr, $css) + { $attr['style'] = isset($attr['style']) ? $attr['style'] : ''; $attr['style'] = $css . $attr['style']; } /** * Retrieves and removes an attribute - * @param $attr Attribute array to process (passed by reference) - * @param $key Key of attribute to confiscate + * @param array &$attr Attribute array to process (passed by reference) + * @param mixed $key Key of attribute to confiscate + * @return mixed */ - public function confiscateAttr(&$attr, $key) { - if (!isset($attr[$key])) return null; + public function confiscateAttr(&$attr, $key) + { + if (!isset($attr[$key])) { + return null; + } $value = $attr[$key]; unset($attr[$key]); return $value; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Background.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php similarity index 55% rename from library/HTMLPurifier/AttrTransform/Background.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php index 0e1ff24a3e..2f72869a5e 100644 --- a/library/HTMLPurifier/AttrTransform/Background.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Background.php @@ -3,21 +3,26 @@ /** * Pre-transform that changes proprietary background attribute to CSS. */ -class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform { - - public function transform($attr, $config, $context) { - - if (!isset($attr['background'])) return $attr; +class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['background'])) { + return $attr; + } $background = $this->confiscateAttr($attr, 'background'); // some validation should happen here $this->prependCSS($attr, "background-image:url($background);"); - return $attr; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/BdoDir.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php similarity index 55% rename from library/HTMLPurifier/AttrTransform/BdoDir.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php index 4d1a05665e..d66c04a5b8 100644 --- a/library/HTMLPurifier/AttrTransform/BdoDir.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php @@ -8,12 +8,20 @@ class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform { - public function transform($attr, $config, $context) { - if (isset($attr['dir'])) return $attr; + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (isset($attr['dir'])) { + return $attr; + } $attr['dir'] = $config->get('Attr.DefaultTextDir'); return $attr; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/BgColor.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php similarity index 55% rename from library/HTMLPurifier/AttrTransform/BgColor.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php index ad3916bb96..0f51fd2cec 100644 --- a/library/HTMLPurifier/AttrTransform/BgColor.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php @@ -3,21 +3,26 @@ /** * Pre-transform that changes deprecated bgcolor attribute to CSS. */ -class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform { - - public function transform($attr, $config, $context) { - - if (!isset($attr['bgcolor'])) return $attr; +class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['bgcolor'])) { + return $attr; + } $bgcolor = $this->confiscateAttr($attr, 'bgcolor'); // some validation should happen here $this->prependCSS($attr, "background-color:$bgcolor;"); - return $attr; - } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php new file mode 100644 index 0000000000..f25cd01955 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php @@ -0,0 +1,47 @@ +<?php + +/** + * Pre-transform that changes converts a boolean attribute to fixed CSS + */ +class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform +{ + /** + * Name of boolean attribute that is trigger. + * @type string + */ + protected $attr; + + /** + * CSS declarations to add to style, needs trailing semicolon. + * @type string + */ + protected $css; + + /** + * @param string $attr attribute name to convert from + * @param string $css CSS declarations to add to style (needs semicolon) + */ + public function __construct($attr, $css) + { + $this->attr = $attr; + $this->css = $css; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + unset($attr[$this->attr]); + $this->prependCSS($attr, $this->css); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Border.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php similarity index 55% rename from library/HTMLPurifier/AttrTransform/Border.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php index 476b0b079b..057dc017fa 100644 --- a/library/HTMLPurifier/AttrTransform/Border.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php @@ -3,16 +3,24 @@ /** * Pre-transform that changes deprecated border attribute to CSS. */ -class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform { - - public function transform($attr, $config, $context) { - if (!isset($attr['border'])) return $attr; +class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['border'])) { + return $attr; + } $border_width = $this->confiscateAttr($attr, 'border'); // some validation should happen here $this->prependCSS($attr, "border:{$border_width}px solid;"); return $attr; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php new file mode 100644 index 0000000000..7ccd0e3fb7 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php @@ -0,0 +1,68 @@ +<?php + +/** + * Generic pre-transform that converts an attribute with a fixed number of + * values (enumerated) to CSS. + */ +class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform +{ + /** + * Name of attribute to transform from. + * @type string + */ + protected $attr; + + /** + * Lookup array of attribute values to CSS. + * @type array + */ + protected $enumToCSS = array(); + + /** + * Case sensitivity of the matching. + * @type bool + * @warning Currently can only be guaranteed to work with ASCII + * values. + */ + protected $caseSensitive = false; + + /** + * @param string $attr Attribute name to transform from + * @param array $enum_to_css Lookup array of attribute values to CSS + * @param bool $case_sensitive Case sensitivity indicator, default false + */ + public function __construct($attr, $enum_to_css, $case_sensitive = false) + { + $this->attr = $attr; + $this->enumToCSS = $enum_to_css; + $this->caseSensitive = (bool)$case_sensitive; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + + $value = trim($attr[$this->attr]); + unset($attr[$this->attr]); + + if (!$this->caseSensitive) { + $value = strtolower($value); + } + + if (!isset($this->enumToCSS[$value])) { + return $attr; + } + $this->prependCSS($attr, $this->enumToCSS[$value]); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/ImgRequired.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php similarity index 77% rename from library/HTMLPurifier/AttrTransform/ImgRequired.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php index 7f0e4b7a59..7df6cb3e1b 100644 --- a/library/HTMLPurifier/AttrTransform/ImgRequired.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php @@ -11,11 +11,19 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform { - public function transform($attr, $config, $context) { - + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { $src = true; if (!isset($attr['src'])) { - if ($config->get('Core.RemoveInvalidImg')) return $attr; + if ($config->get('Core.RemoveInvalidImg')) { + return $attr; + } $attr['src'] = $config->get('Attr.DefaultInvalidImage'); $src = false; } @@ -25,7 +33,7 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform $alt = $config->get('Attr.DefaultImageAlt'); if ($alt === null) { // truncate if the alt is too long - $attr['alt'] = substr(basename($attr['src']),0,40); + $attr['alt'] = substr(basename($attr['src']), 0, 40); } else { $attr['alt'] = $alt; } @@ -33,11 +41,8 @@ class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); } } - return $attr; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/ImgSpace.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php similarity index 60% rename from library/HTMLPurifier/AttrTransform/ImgSpace.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php index fd84c10c36..350b3358fc 100644 --- a/library/HTMLPurifier/AttrTransform/ImgSpace.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php @@ -3,42 +3,59 @@ /** * Pre-transform that changes deprecated hspace and vspace attributes to CSS */ -class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform { - +class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform +{ + /** + * @type string + */ protected $attr; + + /** + * @type array + */ protected $css = array( 'hspace' => array('left', 'right'), 'vspace' => array('top', 'bottom') ); - public function __construct($attr) { + /** + * @param string $attr + */ + public function __construct($attr) + { $this->attr = $attr; if (!isset($this->css[$attr])) { trigger_error(htmlspecialchars($attr) . ' is not valid space attribute'); } } - public function transform($attr, $config, $context) { - - if (!isset($attr[$this->attr])) return $attr; + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } $width = $this->confiscateAttr($attr, $this->attr); // some validation could happen here - if (!isset($this->css[$this->attr])) return $attr; + if (!isset($this->css[$this->attr])) { + return $attr; + } $style = ''; foreach ($this->css[$this->attr] as $suffix) { $property = "margin-$suffix"; $style .= "$property:{$width}px;"; } - $this->prependCSS($attr, $style); - return $attr; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Input.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php similarity index 61% rename from library/HTMLPurifier/AttrTransform/Input.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php index 16829552d1..3ab47ed8c9 100644 --- a/library/HTMLPurifier/AttrTransform/Input.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php @@ -4,17 +4,31 @@ * Performs miscellaneous cross attribute validation and filtering for * input elements. This is meant to be a post-transform. */ -class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform { - +class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform +{ + /** + * @type HTMLPurifier_AttrDef_HTML_Pixels + */ protected $pixels; - public function __construct() { + public function __construct() + { $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); } - public function transform($attr, $config, $context) { - if (!isset($attr['type'])) $t = 'text'; - else $t = strtolower($attr['type']); + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $t = 'text'; + } else { + $t = strtolower($attr['type']); + } if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') { unset($attr['checked']); } @@ -23,8 +37,11 @@ class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform { } if (isset($attr['size']) && $t !== 'text' && $t !== 'password') { $result = $this->pixels->validate($attr['size'], $config, $context); - if ($result === false) unset($attr['size']); - else $attr['size'] = $result; + if ($result === false) { + unset($attr['size']); + } else { + $attr['size'] = $result; + } } if (isset($attr['src']) && $t !== 'image') { unset($attr['src']); @@ -34,7 +51,6 @@ class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform { } return $attr; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/Lang.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php similarity index 68% rename from library/HTMLPurifier/AttrTransform/Lang.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php index 5869e7f820..5b0aff0e40 100644 --- a/library/HTMLPurifier/AttrTransform/Lang.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php @@ -8,9 +8,15 @@ class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform { - public function transform($attr, $config, $context) { - - $lang = isset($attr['lang']) ? $attr['lang'] : false; + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + $lang = isset($attr['lang']) ? $attr['lang'] : false; $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false; if ($lang !== false && $xml_lang === false) { @@ -18,11 +24,8 @@ class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform } elseif ($xml_lang !== false) { $attr['lang'] = $xml_lang; } - return $attr; - } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php new file mode 100644 index 0000000000..853f33549b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Length.php @@ -0,0 +1,45 @@ +<?php + +/** + * Class for handling width/height length attribute transformations to CSS + */ +class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform +{ + + /** + * @type string + */ + protected $name; + + /** + * @type string + */ + protected $cssName; + + public function __construct($name, $css_name = null) + { + $this->name = $name; + $this->cssName = $css_name ? $css_name : $name; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->name])) { + return $attr; + } + $length = $this->confiscateAttr($attr, $this->name); + if (ctype_digit($length)) { + $length .= 'px'; + } + $this->prependCSS($attr, $this->cssName . ":$length;"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php new file mode 100644 index 0000000000..63cce6837a --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php @@ -0,0 +1,33 @@ +<?php + +/** + * Pre-transform that changes deprecated name attribute to ID if necessary + */ +class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform +{ + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // Abort early if we're using relaxed definition of name + if ($config->get('HTML.Attr.Name.UseCDATA')) { + return $attr; + } + if (!isset($attr['name'])) { + return $attr; + } + $id = $this->confiscateAttr($attr, 'name'); + if (isset($attr['id'])) { + return $attr; + } + $attr['id'] = $id; + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php new file mode 100644 index 0000000000..36079b786f --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php @@ -0,0 +1,41 @@ +<?php + +/** + * Post-transform that performs validation to the name attribute; if + * it is present with an equivalent id attribute, it is passed through; + * otherwise validation is performed. + */ +class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform +{ + + public function __construct() + { + $this->idDef = new HTMLPurifier_AttrDef_HTML_ID(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['name'])) { + return $attr; + } + $name = $attr['name']; + if (isset($attr['id']) && $attr['id'] === $name) { + return $attr; + } + $result = $this->idDef->validate($name, $config, $context); + if ($result === false) { + unset($attr['name']); + } else { + $attr['name'] = $result; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php new file mode 100644 index 0000000000..1057ebee1b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php @@ -0,0 +1,52 @@ +<?php + +// must be called POST validation + +/** + * Adds rel="nofollow" to all outbound links. This transform is + * only attached if Attr.Nofollow is TRUE. + */ +class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform +{ + /** + * @type HTMLPurifier_URIParser + */ + private $parser; + + public function __construct() + { + $this->parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + $scheme = $url->getSchemeObj($config, $context); + + if ($scheme->browsable && !$url->isLocal($config, $context)) { + if (isset($attr['rel'])) { + $rels = explode(' ', $attr['rel']); + if (!in_array('nofollow', $rels)) { + $rels[] = 'nofollow'; + } + $attr['rel'] = implode(' ', $rels); + } else { + $attr['rel'] = 'nofollow'; + } + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/SafeEmbed.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php similarity index 56% rename from library/HTMLPurifier/AttrTransform/SafeEmbed.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php index 4da449981f..231c81a3f0 100644 --- a/library/HTMLPurifier/AttrTransform/SafeEmbed.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php @@ -2,9 +2,19 @@ class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform { + /** + * @type string + */ public $name = "SafeEmbed"; - public function transform($attr, $config, $context) { + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { $attr['allowscriptaccess'] = 'never'; $attr['allownetworking'] = 'internal'; $attr['type'] = 'application/x-shockwave-flash'; diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php new file mode 100644 index 0000000000..d1f3a4d2ed --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeObject.php @@ -0,0 +1,28 @@ +<?php + +/** + * Writes default type for all objects. Currently only supports flash. + */ +class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform +{ + /** + * @type string + */ + public $name = "SafeObject"; + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $attr['type'] = 'application/x-shockwave-flash'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTransform/SafeParam.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php similarity index 67% rename from library/HTMLPurifier/AttrTransform/SafeParam.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php index 3f992ec31b..1143b4b493 100644 --- a/library/HTMLPurifier/AttrTransform/SafeParam.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeParam.php @@ -14,14 +14,30 @@ */ class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform { + /** + * @type string + */ public $name = "SafeParam"; + + /** + * @type HTMLPurifier_AttrDef_URI + */ private $uri; - public function __construct() { + public function __construct() + { $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded + $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent')); } - public function transform($attr, $config, $context) { + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { // If we add support for other objects, we'll need to alter the // transforms. switch ($attr['name']) { @@ -33,8 +49,15 @@ class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform case 'allowNetworking': $attr['value'] = 'internal'; break; + case 'allowFullScreen': + if ($config->get('HTML.FlashAllowFullScreen')) { + $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false'; + } else { + $attr['value'] = 'false'; + } + break; case 'wmode': - $attr['value'] = 'window'; + $attr['value'] = $this->wmode->validate($attr['value'], $config, $context); break; case 'movie': case 'src': diff --git a/library/HTMLPurifier/AttrTransform/ScriptRequired.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php similarity index 59% rename from library/HTMLPurifier/AttrTransform/ScriptRequired.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php index 4499050a22..b7057bbf8e 100644 --- a/library/HTMLPurifier/AttrTransform/ScriptRequired.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php @@ -5,7 +5,14 @@ */ class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform { - public function transform($attr, $config, $context) { + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { if (!isset($attr['type'])) { $attr['type'] = 'text/javascript'; } diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php new file mode 100644 index 0000000000..dd63ea89cb --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php @@ -0,0 +1,45 @@ +<?php + +// must be called POST validation + +/** + * Adds target="blank" to all outbound links. This transform is + * only attached if Attr.TargetBlank is TRUE. This works regardless + * of whether or not Attr.AllowedFrameTargets + */ +class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform +{ + /** + * @type HTMLPurifier_URIParser + */ + private $parser; + + public function __construct() + { + $this->parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + $scheme = $url->getSchemeObj($config, $context); + + if ($scheme->browsable && !$url->isBenign($config, $context)) { + $attr['target'] = '_blank'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php new file mode 100644 index 0000000000..6a9f33a0c8 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php @@ -0,0 +1,27 @@ +<?php + +/** + * Sets height/width defaults for <textarea> + */ +class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // Calculated from Firefox + if (!isset($attr['cols'])) { + $attr['cols'] = '22'; + } + if (!isset($attr['rows'])) { + $attr['rows'] = '3'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/AttrTypes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php similarity index 64% rename from library/HTMLPurifier/AttrTypes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php index fc2ea4e588..3b70520b6a 100644 --- a/library/HTMLPurifier/AttrTypes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php @@ -6,7 +6,8 @@ class HTMLPurifier_AttrTypes { /** - * Lookup array of attribute string identifiers to concrete implementations + * Lookup array of attribute string identifiers to concrete implementations. + * @type HTMLPurifier_AttrDef[] */ protected $info = array(); @@ -14,7 +15,15 @@ class HTMLPurifier_AttrTypes * Constructs the info array, supplying default implementations for attribute * types. */ - public function __construct() { + public function __construct() + { + // XXX This is kind of poor, since we don't actually /clone/ + // instances; instead, we use the supplied make() attribute. So, + // the underlying class must know how to deal with arguments. + // With the old implementation of Enum, that ignored its + // arguments when handling a make dispatch, the IAlign + // definition wouldn't work. + // pseudo-types, must be instantiated via shorthand $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum(); $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); @@ -29,6 +38,9 @@ class HTMLPurifier_AttrTypes $this->info['URI'] = new HTMLPurifier_AttrDef_URI(); $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); + $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right'); + $this->info['LAlign'] = self::makeEnum('top,bottom,left,right'); + $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget(); // unimplemented aliases $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); @@ -44,32 +56,39 @@ class HTMLPurifier_AttrTypes $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); } + private static function makeEnum($in) + { + return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in))); + } + /** * Retrieves a type - * @param $type String type name - * @return Object AttrDef for type + * @param string $type String type name + * @return HTMLPurifier_AttrDef Object AttrDef for type */ - public function get($type) { - + public function get($type) + { // determine if there is any extra info tacked on - if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2); - else $string = ''; + if (strpos($type, '#') !== false) { + list($type, $string) = explode('#', $type, 2); + } else { + $string = ''; + } if (!isset($this->info[$type])) { trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR); return; } - return $this->info[$type]->make($string); - } /** * Sets a new implementation for a type - * @param $type String type name - * @param $impl Object AttrDef for type + * @param string $type String type name + * @param HTMLPurifier_AttrDef $impl Object AttrDef for type */ - public function set($type, $impl) { + public function set($type, $impl) + { $this->info[$type] = $impl; } } diff --git a/library/HTMLPurifier/AttrValidator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php similarity index 74% rename from library/HTMLPurifier/AttrValidator.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php index 829a0f8f22..f97dc93edd 100644 --- a/library/HTMLPurifier/AttrValidator.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php @@ -9,17 +9,14 @@ class HTMLPurifier_AttrValidator { /** - * Validates the attributes of a token, returning a modified token + * Validates the attributes of a token, mutating it as necessary. * that has valid tokens - * @param $token Reference to token to validate. We require a reference - * because the operation this class performs on the token are - * not atomic, so the context CurrentToken to be updated - * throughout - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context + * @param HTMLPurifier_Token $token Token to validate. + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context */ - public function validateToken(&$token, &$config, $context) { - + public function validateToken($token, $config, $context) + { $definition = $config->getHTMLDefinition(); $e =& $context->get('ErrorCollector', true); @@ -32,12 +29,15 @@ class HTMLPurifier_AttrValidator // initialize CurrentToken if necessary $current_token =& $context->get('CurrentToken', true); - if (!$current_token) $context->register('CurrentToken', $token); + if (!$current_token) { + $context->register('CurrentToken', $token); + } - if ( - !$token instanceof HTMLPurifier_Token_Start && + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty - ) return $token; + ) { + return; + } // create alias to global definition array, see also $defs // DEFINITION CALL @@ -51,7 +51,9 @@ class HTMLPurifier_AttrValidator foreach ($definition->info_attr_transform_pre as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } @@ -60,7 +62,9 @@ class HTMLPurifier_AttrValidator foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } @@ -77,7 +81,7 @@ class HTMLPurifier_AttrValidator foreach ($attr as $attr_key => $value) { // call the definition - if ( isset($defs[$attr_key]) ) { + if (isset($defs[$attr_key])) { // there is a local definition defined if ($defs[$attr_key] === false) { // We've explicitly been told not to allow this element. @@ -89,15 +93,19 @@ class HTMLPurifier_AttrValidator } else { // validate according to the element's definition $result = $defs[$attr_key]->validate( - $value, $config, $context - ); + $value, + $config, + $context + ); } - } elseif ( isset($d_defs[$attr_key]) ) { + } elseif (isset($d_defs[$attr_key])) { // there is a global definition defined, validate according // to the global definition $result = $d_defs[$attr_key]->validate( - $value, $config, $context - ); + $value, + $config, + $context + ); } else { // system never heard of the attribute? DELETE! $result = false; @@ -107,7 +115,9 @@ class HTMLPurifier_AttrValidator if ($result === false || $result === null) { // this is a generic error message that should replaced // with more specific ones when possible - if ($e) $e->send(E_ERROR, 'AttrValidator: Attribute removed'); + if ($e) { + $e->send(E_ERROR, 'AttrValidator: Attribute removed'); + } // remove the attribute unset($attr[$attr_key]); @@ -137,7 +147,9 @@ class HTMLPurifier_AttrValidator foreach ($definition->info_attr_transform_post as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } @@ -145,14 +157,18 @@ class HTMLPurifier_AttrValidator foreach ($definition->info[$token->name]->attr_transform_post as $transform) { $attr = $transform->transform($o = $attr, $config, $context); if ($e) { - if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } } } $token->attr = $attr; // destroy CurrentToken if we made it ourselves - if (!$current_token) $context->destroy('CurrentToken'); + if (!$current_token) { + $context->destroy('CurrentToken'); + } } diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php new file mode 100644 index 0000000000..707122bb29 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php @@ -0,0 +1,124 @@ +<?php + +// constants are slow, so we use as few as possible +if (!defined('HTMLPURIFIER_PREFIX')) { + define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..')); +} + +// accomodations for versions earlier than 5.0.2 +// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net> +if (!defined('PHP_EOL')) { + switch (strtoupper(substr(PHP_OS, 0, 3))) { + case 'WIN': + define('PHP_EOL', "\r\n"); + break; + case 'DAR': + define('PHP_EOL', "\r"); + break; + default: + define('PHP_EOL', "\n"); + } +} + +/** + * Bootstrap class that contains meta-functionality for HTML Purifier such as + * the autoload function. + * + * @note + * This class may be used without any other files from HTML Purifier. + */ +class HTMLPurifier_Bootstrap +{ + + /** + * Autoload function for HTML Purifier + * @param string $class Class to load + * @return bool + */ + public static function autoload($class) + { + $file = HTMLPurifier_Bootstrap::getPath($class); + if (!$file) { + return false; + } + // Technically speaking, it should be ok and more efficient to + // just do 'require', but Antonio Parraga reports that with + // Zend extensions such as Zend debugger and APC, this invariant + // may be broken. Since we have efficient alternatives, pay + // the cost here and avoid the bug. + require_once HTMLPURIFIER_PREFIX . '/' . $file; + return true; + } + + /** + * Returns the path for a specific class. + * @param string $class Class path to get + * @return string + */ + public static function getPath($class) + { + if (strncmp('HTMLPurifier', $class, 12) !== 0) { + return false; + } + // Custom implementations + if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { + $code = str_replace('_', '-', substr($class, 22)); + $file = 'HTMLPurifier/Language/classes/' . $code . '.php'; + } else { + $file = str_replace('_', '/', $class) . '.php'; + } + if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { + return false; + } + return $file; + } + + /** + * "Pre-registers" our autoloader on the SPL stack. + */ + public static function registerAutoload() + { + $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); + if (($funcs = spl_autoload_functions()) === false) { + spl_autoload_register($autoload); + } elseif (function_exists('spl_autoload_unregister')) { + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + // prepend flag exists, no need for shenanigans + spl_autoload_register($autoload, true, true); + } else { + $buggy = version_compare(PHP_VERSION, '5.2.11', '<'); + $compat = version_compare(PHP_VERSION, '5.1.2', '<=') && + version_compare(PHP_VERSION, '5.1.0', '>='); + foreach ($funcs as $func) { + if ($buggy && is_array($func)) { + // :TRICKY: There are some compatibility issues and some + // places where we need to error out + $reflector = new ReflectionMethod($func[0], $func[1]); + if (!$reflector->isStatic()) { + throw new Exception( + 'HTML Purifier autoloader registrar is not compatible + with non-static object methods due to PHP Bug #44144; + Please do not use HTMLPurifier.autoload.php (or any + file that includes this file); instead, place the code: + spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\')) + after your own autoloaders.' + ); + } + // Suprisingly, spl_autoload_register supports the + // Class::staticMethod callback format, although call_user_func doesn't + if ($compat) { + $func = implode('::', $func); + } + } + spl_autoload_unregister($func); + } + spl_autoload_register($autoload); + foreach ($funcs as $func) { + spl_autoload_register($func); + } + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php new file mode 100644 index 0000000000..07cc941758 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php @@ -0,0 +1,474 @@ +<?php + +/** + * Defines allowed CSS attributes and what their values are. + * @see HTMLPurifier_HTMLDefinition + */ +class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition +{ + + public $type = 'CSS'; + + /** + * Assoc array of attribute name to definition object. + * @type HTMLPurifier_AttrDef[] + */ + public $info = array(); + + /** + * Constructs the info array. The meat of this class. + * @param HTMLPurifier_Config $config + */ + protected function doSetup($config) + { + $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum( + array('left', 'right', 'center', 'justify'), + false + ); + + $border_style = + $this->info['border-bottom-style'] = + $this->info['border-right-style'] = + $this->info['border-left-style'] = + $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( + array( + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + 'inset', + 'outset' + ), + false + ); + + $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); + + $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right', 'both'), + false + ); + $this->info['float'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right'), + false + ); + $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'italic', 'oblique'), + false + ); + $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'small-caps'), + false + ); + + $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('none')), + new HTMLPurifier_AttrDef_CSS_URI() + ) + ); + + $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( + array('inside', 'outside'), + false + ); + $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( + array( + 'disc', + 'circle', + 'square', + 'decimal', + 'lower-roman', + 'upper-roman', + 'lower-alpha', + 'upper-alpha', + 'none' + ), + false + ); + $this->info['list-style-image'] = $uri_or_none; + + $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); + + $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( + array('capitalize', 'uppercase', 'lowercase', 'none'), + false + ); + $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->info['background-image'] = $uri_or_none; + $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( + array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') + ); + $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( + array('scroll', 'fixed') + ); + $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); + + $border_color = + $this->info['border-top-color'] = + $this->info['border-bottom-color'] = + $this->info['border-left-color'] = + $this->info['border-right-color'] = + $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('transparent')), + new HTMLPurifier_AttrDef_CSS_Color() + ) + ); + + $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); + + $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); + + $border_width = + $this->info['border-top-width'] = + $this->info['border-bottom-width'] = + $this->info['border-left-width'] = + $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), + new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative + ) + ); + + $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); + + $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + 'larger', + 'smaller' + ) + ), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); + + $margin = + $this->info['margin-top'] = + $this->info['margin-bottom'] = + $this->info['margin-left'] = + $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ); + + $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); + + // non-negative + $padding = + $this->info['padding-top'] = + $this->info['padding-bottom'] = + $this->info['padding-left'] = + $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); + + $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); + + $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); + + $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ); + $max = $config->get('CSS.MaxImgLength'); + + $this->info['width'] = + $this->info['height'] = + $max === null ? + $trusted_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ), + // For everyone else: + $trusted_wh + ); + + $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); + + $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); + + // this could use specialized code + $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( + array( + 'normal', + 'bold', + 'bolder', + 'lighter', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900' + ), + false + ); + + // MUST be called after other font properties, as it references + // a CSSDefinition object + $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); + + // same here + $this->info['border'] = + $this->info['border-bottom'] = + $this->info['border-top'] = + $this->info['border-left'] = + $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); + + $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( + array('collapse', 'separate') + ); + + $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( + array('top', 'bottom') + ); + + $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( + array('auto', 'fixed') + ); + + $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'baseline', + 'sub', + 'super', + 'top', + 'text-top', + 'middle', + 'bottom', + 'text-bottom' + ) + ), + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); + + $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); + + // These CSS properties don't work on many browsers, but we live + // in THE FUTURE! + $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum( + array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line') + ); + + if ($config->get('CSS.Proprietary')) { + $this->doSetupProprietary($config); + } + + if ($config->get('CSS.AllowTricky')) { + $this->doSetupTricky($config); + } + + if ($config->get('CSS.Trusted')) { + $this->doSetupTrusted($config); + } + + $allow_important = $config->get('CSS.AllowImportant'); + // wrap all attr-defs with decorator that handles !important + foreach ($this->info as $k => $v) { + $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); + } + + $this->setupConfigStuff($config); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupProprietary($config) + { + // Internet Explorer only scrollbar colors + $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + // vendor specific prefixes of opacity + $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + + // only opacity, for now + $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); + + // more CSS3 + $this->info['page-break-after'] = + $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum( + array( + 'auto', + 'always', + 'avoid', + 'left', + 'right' + ) + ); + $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); + + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTricky($config) + { + $this->info['display'] = new HTMLPurifier_AttrDef_Enum( + array( + 'inline', + 'block', + 'list-item', + 'run-in', + 'compact', + 'marker', + 'table', + 'inline-block', + 'inline-table', + 'table-row-group', + 'table-header-group', + 'table-footer-group', + 'table-row', + 'table-column-group', + 'table-column', + 'table-cell', + 'table-caption', + 'none' + ) + ); + $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum( + array('visible', 'hidden', 'collapse') + ); + $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); + $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTrusted($config) + { + $this->info['position'] = new HTMLPurifier_AttrDef_Enum( + array('static', 'relative', 'absolute', 'fixed') + ); + $this->info['top'] = + $this->info['left'] = + $this->info['right'] = + $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); + $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Integer(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); + } + + /** + * Performs extra config-based processing. Based off of + * HTMLPurifier_HTMLDefinition. + * @param HTMLPurifier_Config $config + * @todo Refactor duplicate elements into common class (probably using + * composition, not inheritance). + */ + protected function setupConfigStuff($config) + { + // setup allowed elements + $support = "(for information on implementing this, see the " . + "support forums) "; + $allowed_properties = $config->get('CSS.AllowedProperties'); + if ($allowed_properties !== null) { + foreach ($this->info as $name => $d) { + if (!isset($allowed_properties[$name])) { + unset($this->info[$name]); + } + unset($allowed_properties[$name]); + } + // emit errors + foreach ($allowed_properties as $name => $d) { + // :TODO: Is this htmlspecialchars() call really necessary? + $name = htmlspecialchars($name); + trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); + } + } + + $forbidden_properties = $config->get('CSS.ForbiddenProperties'); + if ($forbidden_properties !== null) { + foreach ($this->info as $name => $d) { + if (isset($forbidden_properties[$name])) { + unset($this->info[$name]); + } + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php similarity index 52% rename from library/HTMLPurifier/ChildDef.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php index c5d5216dab..8eb17b82e1 100644 --- a/library/HTMLPurifier/ChildDef.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php @@ -1,48 +1,52 @@ <?php /** - * Defines allowed child nodes and validates tokens against it. + * Defines allowed child nodes and validates nodes against it. */ abstract class HTMLPurifier_ChildDef { /** * Type of child definition, usually right-most part of class name lowercase. * Used occasionally in terms of context. + * @type string */ public $type; /** - * Bool that indicates whether or not an empty array of children is okay + * Indicates whether or not an empty array of children is okay. * * This is necessary for redundant checking when changes affecting * a child node may cause a parent node to now be disallowed. + * @type bool */ public $allow_empty; /** - * Lookup array of all elements that this definition could possibly allow + * Lookup array of all elements that this definition could possibly allow. + * @type array */ public $elements = array(); /** * Get lookup of tag names that should not close this element automatically. * All other elements will do so. + * @param HTMLPurifier_Config $config HTMLPurifier_Config object + * @return array */ - public function getAllowedElements($config) { + public function getAllowedElements($config) + { return $this->elements; } /** * Validates nodes according to definition and returns modification. * - * @param $tokens_of_children Array of HTMLPurifier_Token - * @param $config HTMLPurifier_Config object - * @param $context HTMLPurifier_Context object - * @return bool true to leave nodes as is - * @return bool false to remove parent node - * @return array of replacement child tokens + * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node + * @param HTMLPurifier_Config $config HTMLPurifier_Config object + * @param HTMLPurifier_Context $context HTMLPurifier_Context object + * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children */ - abstract public function validateChildren($tokens_of_children, $config, $context); + abstract public function validateChildren($children, $config, $context); } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ChildDef/Chameleon.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php similarity index 57% rename from library/HTMLPurifier/ChildDef/Chameleon.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php index 15c364ee33..7439be26b6 100644 --- a/library/HTMLPurifier/ChildDef/Chameleon.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php @@ -14,33 +14,52 @@ class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef /** * Instance of the definition object to use when inline. Usually stricter. + * @type HTMLPurifier_ChildDef_Optional */ public $inline; /** * Instance of the definition object to use when block. + * @type HTMLPurifier_ChildDef_Optional */ public $block; + /** + * @type string + */ public $type = 'chameleon'; /** - * @param $inline List of elements to allow when inline. - * @param $block List of elements to allow when block. + * @param array $inline List of elements to allow when inline. + * @param array $block List of elements to allow when block. */ - public function __construct($inline, $block) { + public function __construct($inline, $block) + { $this->inline = new HTMLPurifier_ChildDef_Optional($inline); - $this->block = new HTMLPurifier_ChildDef_Optional($block); + $this->block = new HTMLPurifier_ChildDef_Optional($block); $this->elements = $this->block->elements; } - public function validateChildren($tokens_of_children, $config, $context) { + /** + * @param HTMLPurifier_Node[] $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function validateChildren($children, $config, $context) + { if ($context->get('IsInline') === false) { return $this->block->validateChildren( - $tokens_of_children, $config, $context); + $children, + $config, + $context + ); } else { return $this->inline->validateChildren( - $tokens_of_children, $config, $context); + $children, + $config, + $context + ); } } } diff --git a/library/HTMLPurifier/ChildDef/Custom.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php similarity index 71% rename from library/HTMLPurifier/ChildDef/Custom.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php index b68047b4b5..128132e96d 100644 --- a/library/HTMLPurifier/ChildDef/Custom.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php @@ -8,28 +8,42 @@ */ class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef { - public $type = 'custom'; - public $allow_empty = false; /** - * Allowed child pattern as defined by the DTD + * @type string + */ + public $type = 'custom'; + + /** + * @type bool + */ + public $allow_empty = false; + + /** + * Allowed child pattern as defined by the DTD. + * @type string */ public $dtd_regex; + /** - * PCRE regex derived from $dtd_regex - * @private + * PCRE regex derived from $dtd_regex. + * @type string */ private $_pcre_regex; + /** * @param $dtd_regex Allowed child pattern from the DTD */ - public function __construct($dtd_regex) { + public function __construct($dtd_regex) + { $this->dtd_regex = $dtd_regex; $this->_compileRegex(); } + /** * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex) */ - protected function _compileRegex() { + protected function _compileRegex() + { $raw = str_replace(' ', '', $this->dtd_regex); if ($raw{0} != '(') { $raw = "($raw)"; @@ -57,33 +71,31 @@ class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef $this->_pcre_regex = $reg; } - public function validateChildren($tokens_of_children, $config, $context) { + + /** + * @param HTMLPurifier_Node[] $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function validateChildren($children, $config, $context) + { $list_of_children = ''; $nesting = 0; // depth into the nest - foreach ($tokens_of_children as $token) { - if (!empty($token->is_whitespace)) continue; - - $is_child = ($nesting == 0); // direct - - if ($token instanceof HTMLPurifier_Token_Start) { - $nesting++; - } elseif ($token instanceof HTMLPurifier_Token_End) { - $nesting--; - } - - if ($is_child) { - $list_of_children .= $token->name . ','; + foreach ($children as $node) { + if (!empty($node->is_whitespace)) { + continue; } + $list_of_children .= $node->name . ','; } // add leading comma to deal with stray comma declarations $list_of_children = ',' . rtrim($list_of_children, ','); $okay = preg_match( - '/^,?'.$this->_pcre_regex.'$/', + '/^,?' . $this->_pcre_regex . '$/', $list_of_children ); - - return (bool) $okay; + return (bool)$okay; } } diff --git a/library/HTMLPurifier/ChildDef/Empty.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php similarity index 58% rename from library/HTMLPurifier/ChildDef/Empty.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php index 13171f6651..a8a6cbdd2c 100644 --- a/library/HTMLPurifier/ChildDef/Empty.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php @@ -9,10 +9,28 @@ */ class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef { + /** + * @type bool + */ public $allow_empty = true; + + /** + * @type string + */ public $type = 'empty'; - public function __construct() {} - public function validateChildren($tokens_of_children, $config, $context) { + + public function __construct() + { + } + + /** + * @param HTMLPurifier_Node[] $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { return array(); } } diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php new file mode 100644 index 0000000000..891b9f6f5b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/List.php @@ -0,0 +1,86 @@ +<?php + +/** + * Definition for list containers ul and ol. + * + * What does this do? The big thing is to handle ol/ul at the top + * level of list nodes, which should be handled specially by /folding/ + * them into the previous list node. We generally shouldn't ever + * see other disallowed elements, because the autoclose behavior + * in MakeWellFormed handles it. + */ +class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef +{ + /** + * @type string + */ + public $type = 'list'; + /** + * @type array + */ + // lying a little bit, so that we can handle ul and ol ourselves + // XXX: This whole business with 'wrap' is all a bit unsatisfactory + public $elements = array('li' => true, 'ul' => true, 'ol' => true); + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + // Flag for subclasses + $this->whitespace = false; + + // if there are no tokens, delete parent node + if (empty($children)) { + return false; + } + + // the new set of children + $result = array(); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + $current_li = false; + + foreach ($children as $node) { + if (!empty($node->is_whitespace)) { + $result[] = $node; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + if ($node->name === 'li') { + // good + $current_li = $node; + $result[] = $node; + } else { + // we want to tuck this into the previous li + // Invariant: we expect the node to be ol/ul + // ToDo: Make this more robust in the case of not ol/ul + // by distinguishing between existing li and li created + // to handle non-list elements; non-list elements should + // not be appended to an existing li; only li created + // for non-list. This distinction is not currently made. + if ($current_li === false) { + $current_li = new HTMLPurifier_Node_Element('li'); + $result[] = $current_li; + } + $current_li->children[] = $node; + $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo + } + } + if (empty($result)) { + return false; + } + if ($all_whitespace) { + return false; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php new file mode 100644 index 0000000000..b9468063b1 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php @@ -0,0 +1,45 @@ +<?php + +/** + * Definition that allows a set of elements, and allows no children. + * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required, + * really, one shouldn't inherit from the other. Only altered behavior + * is to overload a returned false with an array. Thus, it will never + * return false. + */ +class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required +{ + /** + * @type bool + */ + public $allow_empty = true; + + /** + * @type string + */ + public $type = 'optional'; + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + $result = parent::validateChildren($children, $config, $context); + // we assume that $children is not modified + if ($result === false) { + if (empty($children)) { + return true; + } elseif ($this->whitespace) { + return $children; + } else { + return array(); + } + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php new file mode 100644 index 0000000000..0d1c8f5f39 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php @@ -0,0 +1,118 @@ +<?php + +/** + * Definition that allows a set of elements, but disallows empty children. + */ +class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef +{ + /** + * Lookup table of allowed elements. + * @type array + */ + public $elements = array(); + + /** + * Whether or not the last passed node was all whitespace. + * @type bool + */ + protected $whitespace = false; + + /** + * @param array|string $elements List of allowed element names (lowercase). + */ + public function __construct($elements) + { + if (is_string($elements)) { + $elements = str_replace(' ', '', $elements); + $elements = explode('|', $elements); + } + $keys = array_keys($elements); + if ($keys == array_keys($keys)) { + $elements = array_flip($elements); + foreach ($elements as $i => $x) { + $elements[$i] = true; + if (empty($i)) { + unset($elements[$i]); + } // remove blank + } + } + $this->elements = $elements; + } + + /** + * @type bool + */ + public $allow_empty = false; + + /** + * @type string + */ + public $type = 'required'; + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + // Flag for subclasses + $this->whitespace = false; + + // if there are no tokens, delete parent node + if (empty($children)) { + return false; + } + + // the new set of children + $result = array(); + + // whether or not parsed character data is allowed + // this controls whether or not we silently drop a tag + // or generate escaped HTML from it + $pcdata_allowed = isset($this->elements['#PCDATA']); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + $stack = array_reverse($children); + while (!empty($stack)) { + $node = array_pop($stack); + if (!empty($node->is_whitespace)) { + $result[] = $node; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + if (!isset($this->elements[$node->name])) { + // special case text + // XXX One of these ought to be redundant or something + if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) { + $result[] = $node; + continue; + } + // spill the child contents in + // ToDo: Make configurable + if ($node instanceof HTMLPurifier_Node_Element) { + for ($i = count($node->children) - 1; $i >= 0; $i--) { + $stack[] = $node->children[$i]; + } + continue; + } + continue; + } + $result[] = $node; + } + if (empty($result)) { + return false; + } + if ($all_whitespace) { + $this->whitespace = true; + return false; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php new file mode 100644 index 0000000000..3270a46e1b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php @@ -0,0 +1,110 @@ +<?php + +/** + * Takes the contents of blockquote when in strict and reformats for validation. + */ +class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required +{ + /** + * @type array + */ + protected $real_elements; + + /** + * @type array + */ + protected $fake_elements; + + /** + * @type bool + */ + public $allow_empty = true; + + /** + * @type string + */ + public $type = 'strictblockquote'; + + /** + * @type bool + */ + protected $init = false; + + /** + * @param HTMLPurifier_Config $config + * @return array + * @note We don't want MakeWellFormed to auto-close inline elements since + * they might be allowed. + */ + public function getAllowedElements($config) + { + $this->init($config); + return $this->fake_elements; + } + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + $this->init($config); + + // trick the parent class into thinking it allows more + $this->elements = $this->fake_elements; + $result = parent::validateChildren($children, $config, $context); + $this->elements = $this->real_elements; + + if ($result === false) { + return array(); + } + if ($result === true) { + $result = $children; + } + + $def = $config->getHTMLDefinition(); + $block_wrap_name = $def->info_block_wrapper; + $block_wrap = false; + $ret = array(); + + foreach ($result as $node) { + if ($block_wrap === false) { + if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) || + ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) { + $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper); + $ret[] = $block_wrap; + } + } else { + if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) { + $block_wrap = false; + + } + } + if ($block_wrap) { + $block_wrap->children[] = $node; + } else { + $ret[] = $node; + } + } + return $ret; + } + + /** + * @param HTMLPurifier_Config $config + */ + private function init($config) + { + if (!$this->init) { + $def = $config->getHTMLDefinition(); + // allow all inline elements + $this->real_elements = $this->elements; + $this->fake_elements = $def->info_content_sets['Flow']; + $this->fake_elements['#PCDATA'] = true; + $this->init = true; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php new file mode 100644 index 0000000000..3e4a0f2182 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php @@ -0,0 +1,224 @@ +<?php + +/** + * Definition for tables. The general idea is to extract out all of the + * essential bits, and then reconstruct it later. + * + * This is a bit confusing, because the DTDs and the W3C + * validators seem to disagree on the appropriate definition. The + * DTD claims: + * + * (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+) + * + * But actually, the HTML4 spec then has this to say: + * + * The TBODY start tag is always required except when the table + * contains only one table body and no table head or foot sections. + * The TBODY end tag may always be safely omitted. + * + * So the DTD is kind of wrong. The validator is, unfortunately, kind + * of on crack. + * + * The definition changed again in XHTML1.1; and in my opinion, this + * formulation makes the most sense. + * + * caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ )) + * + * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode. + * If we encounter a thead, tfoot or tbody, we are placed in the former + * mode, and we *must* wrap any stray tr segments with a tbody. But if + * we don't run into any of them, just have tr tags is OK. + */ +class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef +{ + /** + * @type bool + */ + public $allow_empty = false; + + /** + * @type string + */ + public $type = 'table'; + + /** + * @type array + */ + public $elements = array( + 'tr' => true, + 'tbody' => true, + 'thead' => true, + 'tfoot' => true, + 'caption' => true, + 'colgroup' => true, + 'col' => true + ); + + public function __construct() + { + } + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + if (empty($children)) { + return false; + } + + // only one of these elements is allowed in a table + $caption = false; + $thead = false; + $tfoot = false; + + // whitespace + $initial_ws = array(); + $after_caption_ws = array(); + $after_thead_ws = array(); + $after_tfoot_ws = array(); + + // as many of these as you want + $cols = array(); + $content = array(); + + $tbody_mode = false; // if true, then we need to wrap any stray + // <tr>s with a <tbody>. + + $ws_accum =& $initial_ws; + + foreach ($children as $node) { + if ($node instanceof HTMLPurifier_Node_Comment) { + $ws_accum[] = $node; + continue; + } + switch ($node->name) { + case 'tbody': + $tbody_mode = true; + // fall through + case 'tr': + $content[] = $node; + $ws_accum =& $content; + break; + case 'caption': + // there can only be one caption! + if ($caption !== false) break; + $caption = $node; + $ws_accum =& $after_caption_ws; + break; + case 'thead': + $tbody_mode = true; + // XXX This breaks rendering properties with + // Firefox, which never floats a <thead> to + // the top. Ever. (Our scheme will float the + // first <thead> to the top.) So maybe + // <thead>s that are not first should be + // turned into <tbody>? Very tricky, indeed. + if ($thead === false) { + $thead = $node; + $ws_accum =& $after_thead_ws; + } else { + // Oops, there's a second one! What + // should we do? Current behavior is to + // transmutate the first and last entries into + // tbody tags, and then put into content. + // Maybe a better idea is to *attach + // it* to the existing thead or tfoot? + // We don't do this, because Firefox + // doesn't float an extra tfoot to the + // bottom like it does for the first one. + $node->name = 'tbody'; + $content[] = $node; + $ws_accum =& $content; + } + break; + case 'tfoot': + // see above for some aveats + $tbody_mode = true; + if ($tfoot === false) { + $tfoot = $node; + $ws_accum =& $after_tfoot_ws; + } else { + $node->name = 'tbody'; + $content[] = $node; + $ws_accum =& $content; + } + break; + case 'colgroup': + case 'col': + $cols[] = $node; + $ws_accum =& $cols; + break; + case '#PCDATA': + // How is whitespace handled? We treat is as sticky to + // the *end* of the previous element. So all of the + // nonsense we have worked on is to keep things + // together. + if (!empty($node->is_whitespace)) { + $ws_accum[] = $node; + } + break; + } + } + + if (empty($content)) { + return false; + } + + $ret = $initial_ws; + if ($caption !== false) { + $ret[] = $caption; + $ret = array_merge($ret, $after_caption_ws); + } + if ($cols !== false) { + $ret = array_merge($ret, $cols); + } + if ($thead !== false) { + $ret[] = $thead; + $ret = array_merge($ret, $after_thead_ws); + } + if ($tfoot !== false) { + $ret[] = $tfoot; + $ret = array_merge($ret, $after_tfoot_ws); + } + + if ($tbody_mode) { + // we have to shuffle tr into tbody + $current_tr_tbody = null; + + foreach($content as $node) { + switch ($node->name) { + case 'tbody': + $current_tr_tbody = null; + $ret[] = $node; + break; + case 'tr': + if ($current_tr_tbody === null) { + $current_tr_tbody = new HTMLPurifier_Node_Element('tbody'); + $ret[] = $current_tr_tbody; + } + $current_tr_tbody->children[] = $node; + break; + case '#PCDATA': + assert($node->is_whitespace); + if ($current_tr_tbody === null) { + $ret[] = $node; + } else { + $current_tr_tbody->children[] = $node; + } + break; + } + } + } else { + $ret = array_merge($ret, $content); + } + + return $ret; + + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Config.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Config.php new file mode 100644 index 0000000000..2b2db0c264 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Config.php @@ -0,0 +1,920 @@ +<?php + +/** + * Configuration object that triggers customizable behavior. + * + * @warning This class is strongly defined: that means that the class + * will fail if an undefined directive is retrieved or set. + * + * @note Many classes that could (although many times don't) use the + * configuration object make it a mandatory parameter. This is + * because a configuration object should always be forwarded, + * otherwise, you run the risk of missing a parameter and then + * being stumped when a configuration directive doesn't work. + * + * @todo Reconsider some of the public member variables + */ +class HTMLPurifier_Config +{ + + /** + * HTML Purifier's version + * @type string + */ + public $version = '4.7.0'; + + /** + * Whether or not to automatically finalize + * the object if a read operation is done. + * @type bool + */ + public $autoFinalize = true; + + // protected member variables + + /** + * Namespace indexed array of serials for specific namespaces. + * @see getSerial() for more info. + * @type string[] + */ + protected $serials = array(); + + /** + * Serial for entire configuration object. + * @type string + */ + protected $serial; + + /** + * Parser for variables. + * @type HTMLPurifier_VarParser_Flexible + */ + protected $parser = null; + + /** + * Reference HTMLPurifier_ConfigSchema for value checking. + * @type HTMLPurifier_ConfigSchema + * @note This is public for introspective purposes. Please don't + * abuse! + */ + public $def; + + /** + * Indexed array of definitions. + * @type HTMLPurifier_Definition[] + */ + protected $definitions; + + /** + * Whether or not config is finalized. + * @type bool + */ + protected $finalized = false; + + /** + * Property list containing configuration directives. + * @type array + */ + protected $plist; + + /** + * Whether or not a set is taking place due to an alias lookup. + * @type bool + */ + private $aliasMode; + + /** + * Set to false if you do not want line and file numbers in errors. + * (useful when unit testing). This will also compress some errors + * and exceptions. + * @type bool + */ + public $chatty = true; + + /** + * Current lock; only gets to this namespace are allowed. + * @type string + */ + private $lock; + + /** + * Constructor + * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines + * what directives are allowed. + * @param HTMLPurifier_PropertyList $parent + */ + public function __construct($definition, $parent = null) + { + $parent = $parent ? $parent : $definition->defaultPlist; + $this->plist = new HTMLPurifier_PropertyList($parent); + $this->def = $definition; // keep a copy around for checking + $this->parser = new HTMLPurifier_VarParser_Flexible(); + } + + /** + * Convenience constructor that creates a config object based on a mixed var + * @param mixed $config Variable that defines the state of the config + * object. Can be: a HTMLPurifier_Config() object, + * an array of directives based on loadArray(), + * or a string filename of an ini file. + * @param HTMLPurifier_ConfigSchema $schema Schema object + * @return HTMLPurifier_Config Configured object + */ + public static function create($config, $schema = null) + { + if ($config instanceof HTMLPurifier_Config) { + // pass-through + return $config; + } + if (!$schema) { + $ret = HTMLPurifier_Config::createDefault(); + } else { + $ret = new HTMLPurifier_Config($schema); + } + if (is_string($config)) { + $ret->loadIni($config); + } elseif (is_array($config)) $ret->loadArray($config); + return $ret; + } + + /** + * Creates a new config object that inherits from a previous one. + * @param HTMLPurifier_Config $config Configuration object to inherit from. + * @return HTMLPurifier_Config object with $config as its parent. + */ + public static function inherit(HTMLPurifier_Config $config) + { + return new HTMLPurifier_Config($config->def, $config->plist); + } + + /** + * Convenience constructor that creates a default configuration object. + * @return HTMLPurifier_Config default object. + */ + public static function createDefault() + { + $definition = HTMLPurifier_ConfigSchema::instance(); + $config = new HTMLPurifier_Config($definition); + return $config; + } + + /** + * Retrieves a value from the configuration. + * + * @param string $key String key + * @param mixed $a + * + * @return mixed + */ + public function get($key, $a = null) + { + if ($a !== null) { + $this->triggerError( + "Using deprecated API: use \$config->get('$key.$a') instead", + E_USER_WARNING + ); + $key = "$key.$a"; + } + if (!$this->finalized) { + $this->autoFinalize(); + } + if (!isset($this->def->info[$key])) { + // can't add % due to SimpleTest bug + $this->triggerError( + 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key), + E_USER_WARNING + ); + return; + } + if (isset($this->def->info[$key]->isAlias)) { + $d = $this->def->info[$key]; + $this->triggerError( + 'Cannot get value from aliased directive, use real name ' . $d->key, + E_USER_ERROR + ); + return; + } + if ($this->lock) { + list($ns) = explode('.', $key); + if ($ns !== $this->lock) { + $this->triggerError( + 'Cannot get value of namespace ' . $ns . ' when lock for ' . + $this->lock . + ' is active, this probably indicates a Definition setup method ' . + 'is accessing directives that are not within its namespace', + E_USER_ERROR + ); + return; + } + } + return $this->plist->get($key); + } + + /** + * Retrieves an array of directives to values from a given namespace + * + * @param string $namespace String namespace + * + * @return array + */ + public function getBatch($namespace) + { + if (!$this->finalized) { + $this->autoFinalize(); + } + $full = $this->getAll(); + if (!isset($full[$namespace])) { + $this->triggerError( + 'Cannot retrieve undefined namespace ' . + htmlspecialchars($namespace), + E_USER_WARNING + ); + return; + } + return $full[$namespace]; + } + + /** + * Returns a SHA-1 signature of a segment of the configuration object + * that uniquely identifies that particular configuration + * + * @param string $namespace Namespace to get serial for + * + * @return string + * @note Revision is handled specially and is removed from the batch + * before processing! + */ + public function getBatchSerial($namespace) + { + if (empty($this->serials[$namespace])) { + $batch = $this->getBatch($namespace); + unset($batch['DefinitionRev']); + $this->serials[$namespace] = sha1(serialize($batch)); + } + return $this->serials[$namespace]; + } + + /** + * Returns a SHA-1 signature for the entire configuration object + * that uniquely identifies that particular configuration + * + * @return string + */ + public function getSerial() + { + if (empty($this->serial)) { + $this->serial = sha1(serialize($this->getAll())); + } + return $this->serial; + } + + /** + * Retrieves all directives, organized by namespace + * + * @warning This is a pretty inefficient function, avoid if you can + */ + public function getAll() + { + if (!$this->finalized) { + $this->autoFinalize(); + } + $ret = array(); + foreach ($this->plist->squash() as $name => $value) { + list($ns, $key) = explode('.', $name, 2); + $ret[$ns][$key] = $value; + } + return $ret; + } + + /** + * Sets a value to configuration. + * + * @param string $key key + * @param mixed $value value + * @param mixed $a + */ + public function set($key, $value, $a = null) + { + if (strpos($key, '.') === false) { + $namespace = $key; + $directive = $value; + $value = $a; + $key = "$key.$directive"; + $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); + } else { + list($namespace) = explode('.', $key); + } + if ($this->isFinalized('Cannot set directive after finalization')) { + return; + } + if (!isset($this->def->info[$key])) { + $this->triggerError( + 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', + E_USER_WARNING + ); + return; + } + $def = $this->def->info[$key]; + + if (isset($def->isAlias)) { + if ($this->aliasMode) { + $this->triggerError( + 'Double-aliases not allowed, please fix '. + 'ConfigSchema bug with' . $key, + E_USER_ERROR + ); + return; + } + $this->aliasMode = true; + $this->set($def->key, $value); + $this->aliasMode = false; + $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); + return; + } + + // Raw type might be negative when using the fully optimized form + // of stdclass, which indicates allow_null == true + $rtype = is_int($def) ? $def : $def->type; + if ($rtype < 0) { + $type = -$rtype; + $allow_null = true; + } else { + $type = $rtype; + $allow_null = isset($def->allow_null); + } + + try { + $value = $this->parser->parse($value, $type, $allow_null); + } catch (HTMLPurifier_VarParserException $e) { + $this->triggerError( + 'Value for ' . $key . ' is of invalid type, should be ' . + HTMLPurifier_VarParser::getTypeName($type), + E_USER_WARNING + ); + return; + } + if (is_string($value) && is_object($def)) { + // resolve value alias if defined + if (isset($def->aliases[$value])) { + $value = $def->aliases[$value]; + } + // check to see if the value is allowed + if (isset($def->allowed) && !isset($def->allowed[$value])) { + $this->triggerError( + 'Value not supported, valid values are: ' . + $this->_listify($def->allowed), + E_USER_WARNING + ); + return; + } + } + $this->plist->set($key, $value); + + // reset definitions if the directives they depend on changed + // this is a very costly process, so it's discouraged + // with finalization + if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { + $this->definitions[$namespace] = null; + } + + $this->serials[$namespace] = false; + } + + /** + * Convenience function for error reporting + * + * @param array $lookup + * + * @return string + */ + private function _listify($lookup) + { + $list = array(); + foreach ($lookup as $name => $b) { + $list[] = $name; + } + return implode(', ', $list); + } + + /** + * Retrieves object reference to the HTML definition. + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawHTMLDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_HTMLDefinition + */ + public function getHTMLDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('HTML', $raw, $optimized); + } + + /** + * Retrieves object reference to the CSS definition + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawCSSDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_CSSDefinition + */ + public function getCSSDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('CSS', $raw, $optimized); + } + + /** + * Retrieves object reference to the URI definition + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawURIDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_URIDefinition + */ + public function getURIDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('URI', $raw, $optimized); + } + + /** + * Retrieves a definition + * + * @param string $type Type of definition: HTML, CSS, etc + * @param bool $raw Whether or not definition should be returned raw + * @param bool $optimized Only has an effect when $raw is true. Whether + * or not to return null if the result is already present in + * the cache. This is off by default for backwards + * compatibility reasons, but you need to do things this + * way in order to ensure that caching is done properly. + * Check out enduser-customize.html for more details. + * We probably won't ever change this default, as much as the + * maybe semantics is the "right thing to do." + * + * @throws HTMLPurifier_Exception + * @return HTMLPurifier_Definition + */ + public function getDefinition($type, $raw = false, $optimized = false) + { + if ($optimized && !$raw) { + throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"); + } + if (!$this->finalized) { + $this->autoFinalize(); + } + // temporarily suspend locks, so we can handle recursive definition calls + $lock = $this->lock; + $this->lock = null; + $factory = HTMLPurifier_DefinitionCacheFactory::instance(); + $cache = $factory->create($type, $this); + $this->lock = $lock; + if (!$raw) { + // full definition + // --------------- + // check if definition is in memory + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + // check if the definition is setup + if ($def->setup) { + return $def; + } else { + $def->setup($this); + if ($def->optimized) { + $cache->add($def, $this); + } + return $def; + } + } + // check if definition is in cache + $def = $cache->get($this); + if ($def) { + // definition in cache, save to memory and return it + $this->definitions[$type] = $def; + return $def; + } + // initialize it + $def = $this->initDefinition($type); + // set it up + $this->lock = $type; + $def->setup($this); + $this->lock = null; + // save in cache + $cache->add($def, $this); + // return it + return $def; + } else { + // raw definition + // -------------- + // check preconditions + $def = null; + if ($optimized) { + if (is_null($this->get($type . '.DefinitionID'))) { + // fatally error out if definition ID not set + throw new HTMLPurifier_Exception( + "Cannot retrieve raw version without specifying %$type.DefinitionID" + ); + } + } + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + if ($def->setup && !$optimized) { + $extra = $this->chatty ? + " (try moving this code block earlier in your initialization)" : + ""; + throw new HTMLPurifier_Exception( + "Cannot retrieve raw definition after it has already been setup" . + $extra + ); + } + if ($def->optimized === null) { + $extra = $this->chatty ? " (try flushing your cache)" : ""; + throw new HTMLPurifier_Exception( + "Optimization status of definition is unknown" . $extra + ); + } + if ($def->optimized !== $optimized) { + $msg = $optimized ? "optimized" : "unoptimized"; + $extra = $this->chatty ? + " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" + : ""; + throw new HTMLPurifier_Exception( + "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra + ); + } + } + // check if definition was in memory + if ($def) { + if ($def->setup) { + // invariant: $optimized === true (checked above) + return null; + } else { + return $def; + } + } + // if optimized, check if definition was in cache + // (because we do the memory check first, this formulation + // is prone to cache slamming, but I think + // guaranteeing that either /all/ of the raw + // setup code or /none/ of it is run is more important.) + if ($optimized) { + // This code path only gets run once; once we put + // something in $definitions (which is guaranteed by the + // trailing code), we always short-circuit above. + $def = $cache->get($this); + if ($def) { + // save the full definition for later, but don't + // return it yet + $this->definitions[$type] = $def; + return null; + } + } + // check invariants for creation + if (!$optimized) { + if (!is_null($this->get($type . '.DefinitionID'))) { + if ($this->chatty) { + $this->triggerError( + 'Due to a documentation error in previous version of HTML Purifier, your ' . + 'definitions are not being cached. If this is OK, you can remove the ' . + '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' . + 'modify your code to use maybeGetRawDefinition, and test if the returned ' . + 'value is null before making any edits (if it is null, that means that a ' . + 'cached version is available, and no raw operations are necessary). See ' . + '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' . + 'Customize</a> for more details', + E_USER_WARNING + ); + } else { + $this->triggerError( + "Useless DefinitionID declaration", + E_USER_WARNING + ); + } + } + } + // initialize it + $def = $this->initDefinition($type); + $def->optimized = $optimized; + return $def; + } + throw new HTMLPurifier_Exception("The impossible happened!"); + } + + /** + * Initialise definition + * + * @param string $type What type of definition to create + * + * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition + * @throws HTMLPurifier_Exception + */ + private function initDefinition($type) + { + // quick checks failed, let's create the object + if ($type == 'HTML') { + $def = new HTMLPurifier_HTMLDefinition(); + } elseif ($type == 'CSS') { + $def = new HTMLPurifier_CSSDefinition(); + } elseif ($type == 'URI') { + $def = new HTMLPurifier_URIDefinition(); + } else { + throw new HTMLPurifier_Exception( + "Definition of $type type not supported" + ); + } + $this->definitions[$type] = $def; + return $def; + } + + public function maybeGetRawDefinition($name) + { + return $this->getDefinition($name, true, true); + } + + /** + * @return HTMLPurifier_HTMLDefinition + */ + public function maybeGetRawHTMLDefinition() + { + return $this->getDefinition('HTML', true, true); + } + + /** + * @return HTMLPurifier_CSSDefinition + */ + public function maybeGetRawCSSDefinition() + { + return $this->getDefinition('CSS', true, true); + } + + /** + * @return HTMLPurifier_URIDefinition + */ + public function maybeGetRawURIDefinition() + { + return $this->getDefinition('URI', true, true); + } + + /** + * Loads configuration values from an array with the following structure: + * Namespace.Directive => Value + * + * @param array $config_array Configuration associative array + */ + public function loadArray($config_array) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } + foreach ($config_array as $key => $value) { + $key = str_replace('_', '.', $key); + if (strpos($key, '.') !== false) { + $this->set($key, $value); + } else { + $namespace = $key; + $namespace_values = $value; + foreach ($namespace_values as $directive => $value2) { + $this->set($namespace .'.'. $directive, $value2); + } + } + } + } + + /** + * Returns a list of array(namespace, directive) for all directives + * that are allowed in a web-form context as per an allowed + * namespaces/directives list. + * + * @param array $allowed List of allowed namespaces/directives + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array + */ + public static function getAllowedDirectivesForForm($allowed, $schema = null) + { + if (!$schema) { + $schema = HTMLPurifier_ConfigSchema::instance(); + } + if ($allowed !== true) { + if (is_string($allowed)) { + $allowed = array($allowed); + } + $allowed_ns = array(); + $allowed_directives = array(); + $blacklisted_directives = array(); + foreach ($allowed as $ns_or_directive) { + if (strpos($ns_or_directive, '.') !== false) { + // directive + if ($ns_or_directive[0] == '-') { + $blacklisted_directives[substr($ns_or_directive, 1)] = true; + } else { + $allowed_directives[$ns_or_directive] = true; + } + } else { + // namespace + $allowed_ns[$ns_or_directive] = true; + } + } + } + $ret = array(); + foreach ($schema->info as $key => $def) { + list($ns, $directive) = explode('.', $key, 2); + if ($allowed !== true) { + if (isset($blacklisted_directives["$ns.$directive"])) { + continue; + } + if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) { + continue; + } + } + if (isset($def->isAlias)) { + continue; + } + if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { + continue; + } + $ret[] = array($ns, $directive); + } + return $ret; + } + + /** + * Loads configuration values from $_GET/$_POST that were posted + * via ConfigForm + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return mixed + */ + public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); + $config = HTMLPurifier_Config::create($ret, $schema); + return $config; + } + + /** + * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + */ + public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); + $this->loadArray($ret); + } + + /** + * Prepares an array from a form into something usable for the more + * strict parts of HTMLPurifier_Config + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array + */ + public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { + if ($index !== false) { + $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); + } + $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); + $ret = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $skey = "$ns.$directive"; + if (!empty($array["Null_$skey"])) { + $ret[$ns][$directive] = null; + continue; + } + if (!isset($array[$skey])) { + continue; + } + $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; + $ret[$ns][$directive] = $value; + } + return $ret; + } + + /** + * Loads configuration values from an ini file + * + * @param string $filename Name of ini file + */ + public function loadIni($filename) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } + $array = parse_ini_file($filename, true); + $this->loadArray($array); + } + + /** + * Checks whether or not the configuration object is finalized. + * + * @param string|bool $error String error message, or false for no error + * + * @return bool + */ + public function isFinalized($error = false) + { + if ($this->finalized && $error) { + $this->triggerError($error, E_USER_ERROR); + } + return $this->finalized; + } + + /** + * Finalizes configuration only if auto finalize is on and not + * already finalized + */ + public function autoFinalize() + { + if ($this->autoFinalize) { + $this->finalize(); + } else { + $this->plist->squash(true); + } + } + + /** + * Finalizes a configuration object, prohibiting further change + */ + public function finalize() + { + $this->finalized = true; + $this->parser = null; + } + + /** + * Produces a nicely formatted error message by supplying the + * stack frame information OUTSIDE of HTMLPurifier_Config. + * + * @param string $msg An error message + * @param int $no An error number + */ + protected function triggerError($msg, $no) + { + // determine previous stack frame + $extra = ''; + if ($this->chatty) { + $trace = debug_backtrace(); + // zip(tail(trace), trace) -- but PHP is not Haskell har har + for ($i = 0, $c = count($trace); $i < $c - 1; $i++) { + // XXX this is not correct on some versions of HTML Purifier + if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') { + continue; + } + $frame = $trace[$i]; + $extra = " invoked on line {$frame['line']} in file {$frame['file']}"; + break; + } + } + trigger_error($msg . $extra, $no); + } + + /** + * Returns a serialized form of the configuration object that can + * be reconstituted. + * + * @return string + */ + public function serialize() + { + $this->getDefinition('HTML'); + $this->getDefinition('CSS'); + $this->getDefinition('URI'); + return serialize($this); + } + +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php similarity index 69% rename from library/HTMLPurifier/ConfigSchema.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php index 67be5c71fd..bfbb0f92f5 100644 --- a/library/HTMLPurifier/ConfigSchema.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php @@ -3,21 +3,24 @@ /** * Configuration definition, defines directives and their defaults. */ -class HTMLPurifier_ConfigSchema { - +class HTMLPurifier_ConfigSchema +{ /** * Defaults of the directives and namespaces. + * @type array * @note This shares the exact same structure as HTMLPurifier_Config::$conf */ public $defaults = array(); /** * The default property list. Do not edit this property list. + * @type array */ public $defaultPlist; /** - * Definition of the directives. The structure of this is: + * Definition of the directives. + * The structure of this is: * * array( * 'Namespace' => array( @@ -44,29 +47,43 @@ class HTMLPurifier_ConfigSchema { * This class is friendly with HTMLPurifier_Config. If you need introspection * about the schema, you're better of using the ConfigSchema_Interchange, * which uses more memory but has much richer information. + * @type array */ public $info = array(); /** * Application-wide singleton + * @type HTMLPurifier_ConfigSchema */ - static protected $singleton; + protected static $singleton; - public function __construct() { + public function __construct() + { $this->defaultPlist = new HTMLPurifier_PropertyList(); } /** * Unserializes the default ConfigSchema. + * @return HTMLPurifier_ConfigSchema */ - public static function makeFromSerial() { - return unserialize(file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser')); + public static function makeFromSerial() + { + $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'); + $r = unserialize($contents); + if (!$r) { + $hash = sha1($contents); + trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR); + } + return $r; } /** * Retrieves an instance of the application-wide configuration definition. + * @param HTMLPurifier_ConfigSchema $prototype + * @return HTMLPurifier_ConfigSchema */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { if ($prototype !== null) { HTMLPurifier_ConfigSchema::$singleton = $prototype; } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { @@ -80,17 +97,19 @@ class HTMLPurifier_ConfigSchema { * @warning Will fail of directive's namespace is defined. * @warning This method's signature is slightly different from the legacy * define() static method! Beware! - * @param $namespace Namespace the directive is in - * @param $name Key of directive - * @param $default Default value of directive - * @param $type Allowed type of the directive. See + * @param string $key Name of directive + * @param mixed $default Default value of directive + * @param string $type Allowed type of the directive. See * HTMLPurifier_DirectiveDef::$type for allowed values - * @param $allow_null Whether or not to allow null values + * @param bool $allow_null Whether or not to allow null values */ - public function add($key, $default, $type, $allow_null) { + public function add($key, $default, $type, $allow_null) + { $obj = new stdclass(); $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; - if ($allow_null) $obj->allow_null = true; + if ($allow_null) { + $obj->allow_null = true; + } $this->info[$key] = $obj; $this->defaults[$key] = $default; $this->defaultPlist->set($key, $default); @@ -101,11 +120,11 @@ class HTMLPurifier_ConfigSchema { * * Directive value aliases are convenient for developers because it lets * them set a directive to several values and get the same result. - * @param $namespace Directive's namespace - * @param $name Name of Directive - * @param $aliases Hash of aliased values to the real alias + * @param string $key Name of Directive + * @param array $aliases Hash of aliased values to the real alias */ - public function addValueAliases($key, $aliases) { + public function addValueAliases($key, $aliases) + { if (!isset($this->info[$key]->aliases)) { $this->info[$key]->aliases = array(); } @@ -118,22 +137,21 @@ class HTMLPurifier_ConfigSchema { * Defines a set of allowed values for a directive. * @warning This is slightly different from the corresponding static * method definition. - * @param $namespace Namespace of directive - * @param $name Name of directive - * @param $allowed Lookup array of allowed values + * @param string $key Name of directive + * @param array $allowed Lookup array of allowed values */ - public function addAllowedValues($key, $allowed) { + public function addAllowedValues($key, $allowed) + { $this->info[$key]->allowed = $allowed; } /** * Defines a directive alias for backwards compatibility - * @param $namespace - * @param $name Directive that will be aliased - * @param $new_namespace - * @param $new_name Directive that the alias will be to + * @param string $key Directive that will be aliased + * @param string $new_key Directive that the alias will be to */ - public function addAlias($key, $new_key) { + public function addAlias($key, $new_key) + { $obj = new stdclass; $obj->key = $new_key; $obj->isAlias = true; @@ -143,7 +161,8 @@ class HTMLPurifier_ConfigSchema { /** * Replaces any stdclass that only has the type property with type integer. */ - public function postProcess() { + public function postProcess() + { foreach ($this->info as $key => $v) { if (count((array) $v) == 1) { $this->info[$key] = $v->type; @@ -152,7 +171,6 @@ class HTMLPurifier_ConfigSchema { } } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php similarity index 86% rename from library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php index c05668a706..d5906cd46d 100644 --- a/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php @@ -7,7 +7,12 @@ class HTMLPurifier_ConfigSchema_Builder_ConfigSchema { - public function build($interchange) { + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @return HTMLPurifier_ConfigSchema + */ + public function build($interchange) + { $schema = new HTMLPurifier_ConfigSchema(); foreach ($interchange->directives as $d) { $schema->add( @@ -38,7 +43,6 @@ class HTMLPurifier_ConfigSchema_Builder_ConfigSchema $schema->postProcess(); return $schema; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php similarity index 52% rename from library/HTMLPurifier/ConfigSchema/Builder/Xml.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php index 244561a372..5fa56f7ddb 100644 --- a/library/HTMLPurifier/ConfigSchema/Builder/Xml.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php @@ -7,10 +7,21 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter { + /** + * @type HTMLPurifier_ConfigSchema_Interchange + */ protected $interchange; + + /** + * @type string + */ private $namespace; - protected function writeHTMLDiv($html) { + /** + * @param string $html + */ + protected function writeHTMLDiv($html) + { $this->startElement('div'); $purifier = HTMLPurifier::getInstance(); @@ -21,12 +32,23 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter $this->endElement(); // div } - protected function export($var) { - if ($var === array()) return 'array()'; + /** + * @param mixed $var + * @return string + */ + protected function export($var) + { + if ($var === array()) { + return 'array()'; + } return var_export($var, true); } - public function build($interchange) { + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + */ + public function build($interchange) + { // global access, only use as last resort $this->interchange = $interchange; @@ -39,19 +61,26 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter $this->buildDirective($directive); } - if ($this->namespace) $this->endElement(); // namespace + if ($this->namespace) { + $this->endElement(); + } // namespace $this->endElement(); // configdoc $this->flush(); } - public function buildDirective($directive) { - + /** + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + */ + public function buildDirective($directive) + { // Kludge, although I suppose having a notion of a "root namespace" // certainly makes things look nicer when documentation is built. // Depends on things being sorted. if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { - if ($this->namespace) $this->endElement(); // namespace + if ($this->namespace) { + $this->endElement(); + } // namespace $this->namespace = $directive->id->getRootNamespace(); $this->startElement('namespace'); $this->writeAttribute('id', $this->namespace); @@ -64,43 +93,52 @@ class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter $this->writeElement('name', $directive->id->getDirective()); $this->startElement('aliases'); - foreach ($directive->aliases as $alias) $this->writeElement('alias', $alias->toString()); + foreach ($directive->aliases as $alias) { + $this->writeElement('alias', $alias->toString()); + } $this->endElement(); // aliases $this->startElement('constraints'); - if ($directive->version) $this->writeElement('version', $directive->version); - $this->startElement('type'); - if ($directive->typeAllowsNull) $this->writeAttribute('allow-null', 'yes'); - $this->text($directive->type); - $this->endElement(); // type - if ($directive->allowed) { - $this->startElement('allowed'); - foreach ($directive->allowed as $value => $x) $this->writeElement('value', $value); - $this->endElement(); // allowed + if ($directive->version) { + $this->writeElement('version', $directive->version); + } + $this->startElement('type'); + if ($directive->typeAllowsNull) { + $this->writeAttribute('allow-null', 'yes'); + } + $this->text($directive->type); + $this->endElement(); // type + if ($directive->allowed) { + $this->startElement('allowed'); + foreach ($directive->allowed as $value => $x) { + $this->writeElement('value', $value); } - $this->writeElement('default', $this->export($directive->default)); - $this->writeAttribute('xml:space', 'preserve'); - if ($directive->external) { - $this->startElement('external'); - foreach ($directive->external as $project) $this->writeElement('project', $project); - $this->endElement(); + $this->endElement(); // allowed + } + $this->writeElement('default', $this->export($directive->default)); + $this->writeAttribute('xml:space', 'preserve'); + if ($directive->external) { + $this->startElement('external'); + foreach ($directive->external as $project) { + $this->writeElement('project', $project); } + $this->endElement(); + } $this->endElement(); // constraints if ($directive->deprecatedVersion) { $this->startElement('deprecated'); - $this->writeElement('version', $directive->deprecatedVersion); - $this->writeElement('use', $directive->deprecatedUse->toString()); + $this->writeElement('version', $directive->deprecatedVersion); + $this->writeElement('use', $directive->deprecatedUse->toString()); $this->endElement(); // deprecated } $this->startElement('description'); - $this->writeHTMLDiv($directive->description); + $this->writeHTMLDiv($directive->description); $this->endElement(); // description $this->endElement(); // directive } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Exception.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php similarity index 100% rename from library/HTMLPurifier/ConfigSchema/Exception.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php diff --git a/library/HTMLPurifier/ConfigSchema/Interchange.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php similarity index 76% rename from library/HTMLPurifier/ConfigSchema/Interchange.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php index 91a5aa7303..0e08ae8fe7 100644 --- a/library/HTMLPurifier/ConfigSchema/Interchange.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange.php @@ -10,18 +10,23 @@ class HTMLPurifier_ConfigSchema_Interchange /** * Name of the application this schema is describing. + * @type string */ public $name; /** * Array of Directive ID => array(directive info) + * @type HTMLPurifier_ConfigSchema_Interchange_Directive[] */ public $directives = array(); /** * Adds a directive array to $directives + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + * @throws HTMLPurifier_ConfigSchema_Exception */ - public function addDirective($directive) { + public function addDirective($directive) + { if (isset($this->directives[$i = $directive->id->toString()])) { throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); } @@ -32,11 +37,11 @@ class HTMLPurifier_ConfigSchema_Interchange * Convenience function to perform standard validation. Throws exception * on failed validation. */ - public function validate() { + public function validate() + { $validator = new HTMLPurifier_ConfigSchema_Validator(); return $validator->validate($this); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php similarity index 65% rename from library/HTMLPurifier/ConfigSchema/Interchange/Directive.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php index ac8be0d970..127a39a673 100644 --- a/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php @@ -7,71 +7,83 @@ class HTMLPurifier_ConfigSchema_Interchange_Directive { /** - * ID of directive, instance of HTMLPurifier_ConfigSchema_Interchange_Id. + * ID of directive. + * @type HTMLPurifier_ConfigSchema_Interchange_Id */ public $id; /** - * String type, e.g. 'integer' or 'istring'. + * Type, e.g. 'integer' or 'istring'. + * @type string */ public $type; /** * Default value, e.g. 3 or 'DefaultVal'. + * @type mixed */ public $default; /** * HTML description. + * @type string */ public $description; /** - * Boolean whether or not null is allowed as a value. + * Whether or not null is allowed as a value. + * @type bool */ public $typeAllowsNull = false; /** - * Lookup table of allowed scalar values, e.g. array('allowed' => true). + * Lookup table of allowed scalar values. + * e.g. array('allowed' => true). * Null if all values are allowed. + * @type array */ public $allowed; /** - * List of aliases for the directive, + * List of aliases for the directive. * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). + * @type HTMLPurifier_ConfigSchema_Interchange_Id[] */ public $aliases = array(); /** * Hash of value aliases, e.g. array('alt' => 'real'). Null if value * aliasing is disabled (necessary for non-scalar types). + * @type array */ public $valueAliases; /** * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. * Null if the directive has always existed. + * @type string */ public $version; /** - * ID of directive that supercedes this old directive, is an instance - * of HTMLPurifier_ConfigSchema_Interchange_Id. Null if not deprecated. + * ID of directive that supercedes this old directive. + * Null if not deprecated. + * @type HTMLPurifier_ConfigSchema_Interchange_Id */ public $deprecatedUse; /** * Version of HTML Purifier this directive was deprecated. Null if not * deprecated. + * @type string */ public $deprecatedVersion; /** * List of external projects this directive depends on, e.g. array('CSSTidy'). + * @type array */ public $external = array(); - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php similarity index 54% rename from library/HTMLPurifier/ConfigSchema/Interchange/Id.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php index b9b3c6f5cf..126f09d957 100644 --- a/library/HTMLPurifier/ConfigSchema/Interchange/Id.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php @@ -6,32 +6,53 @@ class HTMLPurifier_ConfigSchema_Interchange_Id { + /** + * @type string + */ public $key; - public function __construct($key) { + /** + * @param string $key + */ + public function __construct($key) + { $this->key = $key; } /** + * @return string * @warning This is NOT magic, to ensure that people don't abuse SPL and * cause problems for PHP 5.0 support. */ - public function toString() { + public function toString() + { return $this->key; } - public function getRootNamespace() { + /** + * @return string + */ + public function getRootNamespace() + { return substr($this->key, 0, strpos($this->key, ".")); } - public function getDirective() { + /** + * @return string + */ + public function getDirective() + { return substr($this->key, strpos($this->key, ".") + 1); } - public static function make($id) { + /** + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id + */ + public static function make($id) + { return new HTMLPurifier_ConfigSchema_Interchange_Id($id); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php similarity index 67% rename from library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php index 785b72ce8e..655e6dd1b9 100644 --- a/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php @@ -5,21 +5,39 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder /** * Used for processing DEFAULT, nothing else. + * @type HTMLPurifier_VarParser */ protected $varParser; - public function __construct($varParser = null) { + /** + * @param HTMLPurifier_VarParser $varParser + */ + public function __construct($varParser = null) + { $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); } - public static function buildFromDirectory($dir = null) { - $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); + /** + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public static function buildFromDirectory($dir = null) + { + $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); $interchange = new HTMLPurifier_ConfigSchema_Interchange(); return $builder->buildDir($interchange, $dir); } - public function buildDir($interchange, $dir = null) { - if (!$dir) $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema'; + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public function buildDir($interchange, $dir = null) + { + if (!$dir) { + $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema'; + } if (file_exists($dir . '/info.ini')) { $info = parse_ini_file($dir . '/info.ini'); $interchange->name = $info['name']; @@ -39,24 +57,30 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder foreach ($files as $file) { $this->buildFile($interchange, $dir . '/' . $file); } - return $interchange; } - public function buildFile($interchange, $file) { + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $file + */ + public function buildFile($interchange, $file) + { $parser = new HTMLPurifier_StringHashParser(); $this->build( $interchange, - new HTMLPurifier_StringHash( $parser->parseFile($file) ) + new HTMLPurifier_StringHash($parser->parseFile($file)) ); } /** * Builds an interchange object based on a hash. - * @param $interchange HTMLPurifier_ConfigSchema_Interchange object to build - * @param $hash HTMLPurifier_ConfigSchema_StringHash source data + * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build + * @param HTMLPurifier_StringHash $hash source data + * @throws HTMLPurifier_ConfigSchema_Exception */ - public function build($interchange, $hash) { + public function build($interchange, $hash) + { if (!$hash instanceof HTMLPurifier_StringHash) { $hash = new HTMLPurifier_StringHash($hash); } @@ -75,7 +99,13 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder $this->_findUnused($hash); } - public function buildDirective($interchange, $hash) { + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param HTMLPurifier_StringHash $hash + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function buildDirective($interchange, $hash) + { $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive(); // These are required elements: @@ -84,7 +114,9 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder if (isset($hash['TYPE'])) { $type = explode('/', $hash->offsetGet('TYPE')); - if (isset($type[1])) $directive->typeAllowsNull = true; + if (isset($type[1])) { + $directive->typeAllowsNull = true; + } $directive->type = $type[0]; } else { throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); @@ -92,7 +124,11 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder if (isset($hash['DEFAULT'])) { try { - $directive->default = $this->varParser->parse($hash->offsetGet('DEFAULT'), $directive->type, $directive->typeAllowsNull); + $directive->default = $this->varParser->parse( + $hash->offsetGet('DEFAULT'), + $directive->type, + $directive->typeAllowsNull + ); } catch (HTMLPurifier_VarParserException $e) { throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'"); } @@ -139,34 +175,45 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder /** * Evaluates an array PHP code string without array() wrapper + * @param string $contents */ - protected function evalArray($contents) { - return eval('return array('. $contents .');'); + protected function evalArray($contents) + { + return eval('return array(' . $contents . ');'); } /** * Converts an array list into a lookup array. + * @param array $array + * @return array */ - protected function lookup($array) { + protected function lookup($array) + { $ret = array(); - foreach ($array as $val) $ret[$val] = true; + foreach ($array as $val) { + $ret[$val] = true; + } return $ret; } /** * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id * object based on a string Id. + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id */ - protected function id($id) { + protected function id($id) + { return HTMLPurifier_ConfigSchema_Interchange_Id::make($id); } /** * Triggers errors for any unused keys passed in the hash; such keys * may indicate typos, missing values, etc. - * @param $hash Instance of ConfigSchema_StringHash to check. + * @param HTMLPurifier_StringHash $hash Hash to check. */ - protected function _findUnused($hash) { + protected function _findUnused($hash) + { $accessed = $hash->getAccessed(); foreach ($hash as $k => $v) { if (!isset($accessed[$k])) { @@ -174,7 +221,6 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder } } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/Validator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php similarity index 73% rename from library/HTMLPurifier/ConfigSchema/Validator.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php index f374f6a022..fb31277889 100644 --- a/library/HTMLPurifier/ConfigSchema/Validator.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php @@ -12,36 +12,48 @@ class HTMLPurifier_ConfigSchema_Validator { /** - * Easy to access global objects. + * @type HTMLPurifier_ConfigSchema_Interchange */ - protected $interchange, $aliases; + protected $interchange; + + /** + * @type array + */ + protected $aliases; /** * Context-stack to provide easy to read error messages. + * @type array */ protected $context = array(); /** - * HTMLPurifier_VarParser to test default's type. + * to test default's type. + * @type HTMLPurifier_VarParser */ protected $parser; - public function __construct() { + public function __construct() + { $this->parser = new HTMLPurifier_VarParser(); } /** - * Validates a fully-formed interchange object. Throws an - * HTMLPurifier_ConfigSchema_Exception if there's a problem. + * Validates a fully-formed interchange object. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @return bool */ - public function validate($interchange) { + public function validate($interchange) + { $this->interchange = $interchange; $this->aliases = array(); // PHP is a bit lax with integer <=> string conversions in // arrays, so we don't use the identical !== comparison foreach ($interchange->directives as $i => $directive) { $id = $directive->id->toString(); - if ($i != $id) $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); + if ($i != $id) { + $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); + } $this->validateDirective($directive); } return true; @@ -49,8 +61,10 @@ class HTMLPurifier_ConfigSchema_Validator /** * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. + * @param HTMLPurifier_ConfigSchema_Interchange_Id $id */ - public function validateId($id) { + public function validateId($id) + { $id_string = $id->toString(); $this->context[] = "id '$id_string'"; if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { @@ -67,8 +81,10 @@ class HTMLPurifier_ConfigSchema_Validator /** * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirective($d) { + public function validateDirective($d) + { $id = $d->id->toString(); $this->context[] = "directive '$id'"; $this->validateId($d->id); @@ -108,9 +124,13 @@ class HTMLPurifier_ConfigSchema_Validator /** * Extra validation if $allowed member variable of * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirectiveAllowed($d) { - if (is_null($d->allowed)) return; + public function validateDirectiveAllowed($d) + { + if (is_null($d->allowed)) { + return; + } $this->with($d, 'allowed') ->assertNotEmpty() ->assertIsLookup(); // handled by InterchangeBuilder @@ -119,7 +139,9 @@ class HTMLPurifier_ConfigSchema_Validator } $this->context[] = 'allowed'; foreach ($d->allowed as $val => $x) { - if (!is_string($val)) $this->error("value $val", 'must be a string'); + if (!is_string($val)) { + $this->error("value $val", 'must be a string'); + } } array_pop($this->context); } @@ -127,15 +149,23 @@ class HTMLPurifier_ConfigSchema_Validator /** * Extra validation if $valueAliases member variable of * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirectiveValueAliases($d) { - if (is_null($d->valueAliases)) return; + public function validateDirectiveValueAliases($d) + { + if (is_null($d->valueAliases)) { + return; + } $this->with($d, 'valueAliases') ->assertIsArray(); // handled by InterchangeBuilder $this->context[] = 'valueAliases'; foreach ($d->valueAliases as $alias => $real) { - if (!is_string($alias)) $this->error("alias $alias", 'must be a string'); - if (!is_string($real)) $this->error("alias target $real from alias '$alias'", 'must be a string'); + if (!is_string($alias)) { + $this->error("alias $alias", 'must be a string'); + } + if (!is_string($real)) { + $this->error("alias target $real from alias '$alias'", 'must be a string'); + } if ($alias === $real) { $this->error("alias '$alias'", "must not be an alias to itself"); } @@ -155,8 +185,10 @@ class HTMLPurifier_ConfigSchema_Validator /** * Extra validation if $aliases member variable of * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d */ - public function validateDirectiveAliases($d) { + public function validateDirectiveAliases($d) + { $this->with($d, 'aliases') ->assertIsArray(); // handled by InterchangeBuilder $this->context[] = 'aliases'; @@ -180,27 +212,37 @@ class HTMLPurifier_ConfigSchema_Validator /** * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom * for validating simple member variables of objects. + * @param $obj + * @param $member + * @return HTMLPurifier_ConfigSchema_ValidatorAtom */ - protected function with($obj, $member) { + protected function with($obj, $member) + { return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); } /** * Emits an error, providing helpful context. + * @throws HTMLPurifier_ConfigSchema_Exception */ - protected function error($target, $msg) { - if ($target !== false) $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); - else $prefix = ucfirst($this->getFormattedContext()); + protected function error($target, $msg) + { + if ($target !== false) { + $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); + } else { + $prefix = ucfirst($this->getFormattedContext()); + } throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); } /** * Returns a formatted context string. + * @return string */ - protected function getFormattedContext() { + protected function getFormattedContext() + { return implode(' in ', array_reverse($this->context)); } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php new file mode 100644 index 0000000000..c9aa3644af --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php @@ -0,0 +1,130 @@ +<?php + +/** + * Fluent interface for validating the contents of member variables. + * This should be immutable. See HTMLPurifier_ConfigSchema_Validator for + * use-cases. We name this an 'atom' because it's ONLY for validations that + * are independent and usually scalar. + */ +class HTMLPurifier_ConfigSchema_ValidatorAtom +{ + /** + * @type string + */ + protected $context; + + /** + * @type object + */ + protected $obj; + + /** + * @type string + */ + protected $member; + + /** + * @type mixed + */ + protected $contents; + + public function __construct($context, $obj, $member) + { + $this->context = $context; + $this->obj = $obj; + $this->member = $member; + $this->contents =& $obj->$member; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsString() + { + if (!is_string($this->contents)) { + $this->error('must be a string'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsBool() + { + if (!is_bool($this->contents)) { + $this->error('must be a boolean'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsArray() + { + if (!is_array($this->contents)) { + $this->error('must be an array'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotNull() + { + if ($this->contents === null) { + $this->error('must not be null'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertAlnum() + { + $this->assertIsString(); + if (!ctype_alnum($this->contents)) { + $this->error('must be alphanumeric'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotEmpty() + { + if (empty($this->contents)) { + $this->error('must not be empty'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsLookup() + { + $this->assertIsArray(); + foreach ($this->contents as $v) { + if ($v !== true) { + $this->error('must be a lookup array'); + } + } + return $this; + } + + /** + * @param string $msg + * @throws HTMLPurifier_ConfigSchema_Exception + */ + protected function error($msg) + { + throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser new file mode 100644 index 0000000000000000000000000000000000000000..1e6ccd22755dfa27722e17f457a00ea42bdc1d6f GIT binary patch literal 15305 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 literal 0 HcmV?d00001 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt new file mode 100644 index 0000000000..6367fe23c9 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt @@ -0,0 +1,14 @@ +AutoFormat.RemoveEmpty.Predicate +TYPE: hash +VERSION: 4.7.0 +DEFAULT: array('colgroup' => array(), 'th' => array(), 'td' => array(), 'iframe' => array('src')) +--DESCRIPTION-- +<p> + Given that an element has no contents, it will be removed by default, unless + this predicate dictates otherwise. The predicate can either be an associative + map from tag name to list of attributes that must be present for the element + to be considered preserved: thus, the default always preserves <code>colgroup</code>, + <code>th</code> and <code>td</code>, and also <code>iframe</code> if it + has a <code>src</code>. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt new file mode 100644 index 0000000000..3fd4654065 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt @@ -0,0 +1,12 @@ +CSS.AllowedFonts +TYPE: lookup/null +VERSION: 4.3.0 +DEFAULT: NULL +--DESCRIPTION-- +<p> + Allows you to manually specify a set of allowed fonts. If + <code>NULL</code>, all fonts are allowed. This directive + affects generic names (serif, sans-serif, monospace, cursive, + fantasy) as well as specific font families. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt new file mode 100644 index 0000000000..f1f5c5f12b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt @@ -0,0 +1,13 @@ +CSS.ForbiddenProperties +TYPE: lookup +VERSION: 4.2.0 +DEFAULT: array() +--DESCRIPTION-- +<p> + This is the logical inverse of %CSS.AllowedProperties, and it will + override that directive or any other directive. If possible, + %CSS.AllowedProperties is recommended over this directive, + because it can sometimes be difficult to tell whether or not you've + forbidden all of the CSS properties you truly would like to disallow. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt new file mode 100644 index 0000000000..e733a61e8a --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt @@ -0,0 +1,9 @@ +CSS.Trusted +TYPE: bool +VERSION: 4.2.1 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user's CSS input is trusted or not. If the +input is trusted, a more expansive set of allowed properties. See +also %HTML.Trusted. +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt new file mode 100644 index 0000000000..b2b83d9ab6 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt @@ -0,0 +1,11 @@ +Cache.SerializerPermissions +TYPE: int +VERSION: 4.3.0 +DEFAULT: 0755 +--DESCRIPTION-- + +<p> + Directory permissions of the files and directories created inside + the DefinitionCache/Serializer or other custom serializer path. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt new file mode 100644 index 0000000000..2c910cc7de --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt @@ -0,0 +1,16 @@ +Core.AllowHostnameUnderscore +TYPE: bool +VERSION: 4.6.0 +DEFAULT: false +--DESCRIPTION-- +<p> + By RFC 1123, underscores are not permitted in host names. + (This is in contrast to the specification for DNS, RFC + 2181, which allows underscores.) + However, most browsers do the right thing when faced with + an underscore in the host name, and so some poorly written + websites are written with the expectation this should work. + Setting this parameter to true relaxes our allowed character + check so that underscores are permitted. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt similarity index 84% rename from library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt index 08b381d34c..c572c14ec1 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt @@ -24,5 +24,6 @@ array ( --DESCRIPTION-- Lookup array of color names to six digit hexadecimal number corresponding -to color, with preceding hash mark. Used when parsing colors. +to color, with preceding hash mark. Used when parsing colors. The lookup +is done in a case-insensitive manner. --# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt new file mode 100644 index 0000000000..1cd4c2c964 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt @@ -0,0 +1,14 @@ +Core.DisableExcludes +TYPE: bool +DEFAULT: false +VERSION: 4.5.0 +--DESCRIPTION-- +<p> + This directive disables SGML-style exclusions, e.g. the exclusion of + <code><object></code> in any descendant of a + <code><pre></code> tag. Disabling excludes will allow some + invalid documents to pass through HTML Purifier, but HTML Purifier + will also be less likely to accidentally remove large documents during + processing. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt new file mode 100644 index 0000000000..ce243c35dc --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt @@ -0,0 +1,9 @@ +Core.EnableIDNA +TYPE: bool +DEFAULT: false +VERSION: 4.4.0 +--DESCRIPTION-- +Allows international domain names in URLs. This configuration option +requires the PEAR Net_IDNA2 module to be installed. It operates by +punycoding any internationalized host names for maximum portability. +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt similarity index 62% rename from library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt index 4d5b5055cd..a3881be75c 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt @@ -2,9 +2,11 @@ Core.EscapeInvalidChildren TYPE: bool DEFAULT: false --DESCRIPTION-- -When true, a child is found that is not allowed in the context of the +<p><strong>Warning:</strong> this configuration option is no longer does anything as of 4.6.0.</p> + +<p>When true, a child is found that is not allowed in the context of the parent element will be transformed into text as if it were ASCII. When false, that element and all internal tags will be dropped, though text will be preserved. There is no option for dropping the element but preserving -child nodes. +child nodes.</p> --# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt new file mode 100644 index 0000000000..d77f5360d7 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt @@ -0,0 +1,11 @@ +Core.NormalizeNewlines +TYPE: bool +VERSION: 4.2.0 +DEFAULT: true +--DESCRIPTION-- +<p> + Whether or not to normalize newlines to the operating + system default. When <code>false</code>, HTML Purifier + will attempt to preserve mixed newline files. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt new file mode 100644 index 0000000000..3397d9f71f --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt @@ -0,0 +1,11 @@ +Core.RemoveProcessingInstructions +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +Instead of escaping processing instructions in the form <code><? ... +?></code>, remove it out-right. This may be useful if the HTML +you are validating contains XML processing instruction gunk, however, +it can also be user-unfriendly for people attempting to post PHP +snippets. +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt similarity index 65% rename from library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt index 7fa6536b2c..321eaa2d80 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt @@ -3,6 +3,11 @@ TYPE: bool VERSION: 3.1.0 DEFAULT: false --DESCRIPTION-- +<p> + <strong>Warning:</strong> Deprecated in favor of %HTML.SafeObject and + %Output.FlashCompat (turn both on to allow YouTube videos and other + Flash content). +</p> <p> This directive enables YouTube video embedding in HTML Purifier. Check <a href="http://htmlpurifier.org/docs/enduser-youtube.html">this document diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt similarity index 54% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt index 3e231d2d16..0b2c106da5 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt @@ -5,11 +5,14 @@ DEFAULT: NULL --DESCRIPTION-- <p> - This is a convenience directive that rolls the functionality of - %HTML.AllowedElements and %HTML.AllowedAttributes into one directive. + This is a preferred convenience directive that combines + %HTML.AllowedElements and %HTML.AllowedAttributes. Specify elements and attributes that are allowed using: - <code>element1[attr1|attr2],element2...</code>. You can also use - newlines instead of commas to separate elements. + <code>element1[attr1|attr2],element2...</code>. For example, + if you would like to only allow paragraphs and links, specify + <code>a[href],p</code>. You can specify attributes that apply + to all elements using an asterisk, e.g. <code>*[lang]</code>. + You can also use newlines instead of commas to separate elements. </p> <p> <strong>Warning</strong>: diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt new file mode 100644 index 0000000000..140e21423e --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt @@ -0,0 +1,10 @@ +HTML.AllowedComments +TYPE: lookup +VERSION: 4.4.0 +DEFAULT: array() +--DESCRIPTION-- +A whitelist which indicates what explicit comment bodies should be +allowed, modulo leading and trailing whitespace. See also %HTML.AllowedCommentsRegexp +(these directives are union'ed together, so a comment is considered +valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt new file mode 100644 index 0000000000..f22e977d43 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt @@ -0,0 +1,15 @@ +HTML.AllowedCommentsRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +A regexp, which if it matches the body of a comment, indicates that +it should be allowed. Trailing and leading spaces are removed prior +to running this regular expression. +<strong>Warning:</strong> Make sure you specify +correct anchor metacharacters <code>^regex$</code>, otherwise you may accept +comments that you did not mean to! In particular, the regex <code>/foo|bar/</code> +is probably not sufficiently strict, since it also allows <code>foobar</code>. +See also %HTML.AllowedComments (these directives are union'ed together, +so a comment is considered valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt new file mode 100644 index 0000000000..1d3fa7907d --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt @@ -0,0 +1,23 @@ +HTML.AllowedElements +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- +<p> + If HTML Purifier's tag set is unsatisfactory for your needs, you can + overload it with your own list of tags to allow. If you change + this, you probably also want to change %HTML.AllowedAttributes; see + also %HTML.Allowed which lets you set allowed elements and + attributes at the same time. +</p> +<p> + If you attempt to allow an element that HTML Purifier does not know + about, HTML Purifier will raise an error. You will need to manually + tell HTML Purifier about this element by using the + <a href="http://htmlpurifier.org/docs/enduser-customize.html">advanced customization features.</a> +</p> +<p> + <strong>Warning:</strong> If another directive conflicts with the + elements here, <em>that</em> directive will win and override. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt similarity index 72% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt index a64e3d7c36..6ed70b599f 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt @@ -4,6 +4,6 @@ VERSION: 2.0.1 DEFAULT: NULL --DESCRIPTION-- -A custom doctype for power-users who defined there own document +A custom doctype for power-users who defined their own document type. This directive only applies when %HTML.Doctype is blank. --# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt new file mode 100644 index 0000000000..7878dc0bf6 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt @@ -0,0 +1,11 @@ +HTML.FlashAllowFullScreen +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +<p> + Whether or not to permit embedded Flash content from + %HTML.SafeObject to expand to the full screen. Corresponds to + the <code>allowFullScreen</code> parameter. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt new file mode 100644 index 0000000000..700b30924a --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt @@ -0,0 +1,7 @@ +HTML.Nofollow +TYPE: bool +VERSION: 4.3.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, nofollow rel attributes are added to all outgoing links. +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt new file mode 100644 index 0000000000..5eb6ec2b5a --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt @@ -0,0 +1,13 @@ +HTML.SafeIframe +TYPE: bool +VERSION: 4.4.0 +DEFAULT: false +--DESCRIPTION-- +<p> + Whether or not to permit iframe tags in untrusted documents. This + directive must be accompanied by a whitelist of permitted iframes, + such as %URI.SafeIframeRegexp, otherwise it will fatally error. + This directive has no effect on strict doctypes, as iframes are not + valid. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt new file mode 100644 index 0000000000..5ebc7a19d5 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt @@ -0,0 +1,10 @@ +HTML.SafeScripting +TYPE: lookup +VERSION: 4.5.0 +DEFAULT: array() +--DESCRIPTION-- +<p> + Whether or not to permit script tags to external scripts in documents. + Inline scripting is not allowed, and the script must match an explicit whitelist. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt new file mode 100644 index 0000000000..587a16778b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt @@ -0,0 +1,8 @@ +HTML.TargetBlank +TYPE: bool +VERSION: 4.4.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, <code>target=blank</code> attributes are added to all outgoing links. +(This includes links from an HTTPS version of a page to an HTTP version.) +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt similarity index 91% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt index 89133b1a38..1db9237e9e 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt @@ -5,4 +5,5 @@ DEFAULT: false --DESCRIPTION-- Indicates whether or not the user input is trusted or not. If the input is trusted, a more expansive set of allowed tags and attributes will be used. +See also %CSS.Trusted. --# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt new file mode 100644 index 0000000000..d6f0d9f295 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt @@ -0,0 +1,15 @@ +Output.FixInnerHTML +TYPE: bool +VERSION: 4.3.0 +DEFAULT: true +--DESCRIPTION-- +<p> + If true, HTML Purifier will protect against Internet Explorer's + mishandling of the <code>innerHTML</code> attribute by appending + a space to any attribute that does not contain angled brackets, spaces + or quotes, but contains a backtick. This slightly changes the + semantics of any given attribute, so if this is unacceptable and + you do not use <code>innerHTML</code> on any of your pages, you can + turn this directive off. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt similarity index 74% rename from library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt index ae3a913f24..666635a5ff 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt @@ -12,6 +12,6 @@ array ( --DESCRIPTION-- Whitelist that defines the schemes that a URI is allowed to have. This prevents XSS attacks from using pseudo-schemes like javascript or mocha. -There is also support for the <code>data</code> URI scheme, but it is not -enabled by default. +There is also support for the <code>data</code> and <code>file</code> +URI schemes, but they are not enabled by default. --# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt similarity index 64% rename from library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt index 51e6ea91f7..f891de4996 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt @@ -1,12 +1,15 @@ URI.DisableResources TYPE: bool -VERSION: 1.3.0 +VERSION: 4.2.0 DEFAULT: false --DESCRIPTION-- - <p> Disables embedding resources, essentially meaning no pictures. You can still link to them though. See %URI.DisableExternalResources for why this might be a good idea. </p> +<p> + <em>Note:</em> While this directive has been available since 1.3.0, + it didn't actually start doing anything until 4.2.0. +</p> --# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt similarity index 93% rename from library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt index 0d00f62ea8..1e17c1d461 100644 --- a/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -11,7 +11,7 @@ DEFAULT: NULL to check if a URI has passed through HTML Purifier with this line: </p> -<pre>$checksum === sha1($secret_key . ':' . $url)</pre> +<pre>$checksum === hash_hmac("sha256", $url, $secret_key)</pre> <p> If the output is TRUE, the redirector script should accept the URI. diff --git a/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt new file mode 100644 index 0000000000..79084832be --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt @@ -0,0 +1,22 @@ +URI.SafeIframeRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +<p> + A PCRE regular expression that will be matched against an iframe URI. This is + a relatively inflexible scheme, but works well enough for the most common + use-case of iframes: embedded video. This directive only has an effect if + %HTML.SafeIframe is enabled. Here are some example values: +</p> +<ul> + <li><code>%^http://www.youtube.com/embed/%</code> - Allow YouTube videos</li> + <li><code>%^http://player.vimeo.com/video/%</code> - Allow Vimeo videos</li> + <li><code>%^http://(www.youtube.com/embed/|player.vimeo.com/video/)%</code> - Allow both</li> +</ul> +<p> + Note that this directive does not give you enough granularity to, say, disable + all <code>autoplay</code> videos. Pipe up on the HTML Purifier forums if this + is a capability you want. +</p> +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema/info.ini b/library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini similarity index 100% rename from library/HTMLPurifier/ConfigSchema/schema/info.ini rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini diff --git a/library/HTMLPurifier/ContentSets.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php similarity index 76% rename from library/HTMLPurifier/ContentSets.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php index 3b6e96f5f5..543e3f8f11 100644 --- a/library/HTMLPurifier/ContentSets.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php @@ -7,35 +7,42 @@ class HTMLPurifier_ContentSets { /** - * List of content set strings (pipe seperators) indexed by name. + * List of content set strings (pipe separators) indexed by name. + * @type array */ public $info = array(); /** * List of content set lookups (element => true) indexed by name. + * @type array * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets */ public $lookup = array(); /** - * Synchronized list of defined content sets (keys of info) + * Synchronized list of defined content sets (keys of info). + * @type array */ protected $keys = array(); /** - * Synchronized list of defined content values (values of info) + * Synchronized list of defined content values (values of info). + * @type array */ protected $values = array(); /** * Merges in module's content sets, expands identifiers in the content * sets and populates the keys, values and lookup member variables. - * @param $modules List of HTMLPurifier_HTMLModule + * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule */ - public function __construct($modules) { - if (!is_array($modules)) $modules = array($modules); + public function __construct($modules) + { + if (!is_array($modules)) { + $modules = array($modules); + } // populate content_sets based on module hints // sorry, no way of overloading - foreach ($modules as $module_i => $module) { + foreach ($modules as $module) { foreach ($module->content_sets as $key => $value) { $temp = $this->convertToLookup($value); if (isset($this->lookup[$key])) { @@ -70,11 +77,14 @@ class HTMLPurifier_ContentSets /** * Accepts a definition; generates and assigns a ChildDef for it - * @param $def HTMLPurifier_ElementDef reference - * @param $module Module that defined the ElementDef + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef */ - public function generateChildDef(&$def, $module) { - if (!empty($def->child)) return; // already done! + public function generateChildDef(&$def, $module) + { + if (!empty($def->child)) { // already done! + return; + } $content_model = $def->content_model; if (is_string($content_model)) { // Assume that $this->keys is alphanumeric @@ -89,7 +99,8 @@ class HTMLPurifier_ContentSets $def->child = $this->getChildDef($def, $module); } - public function generateChildDefCallback($matches) { + public function generateChildDefCallback($matches) + { return $this->info[$matches[0]]; } @@ -98,10 +109,12 @@ class HTMLPurifier_ContentSets * member variables in HTMLPurifier_ElementDef * @note This will also defer to modules for custom HTMLPurifier_ChildDef * subclasses that need content set expansion - * @param $def HTMLPurifier_ElementDef to have ChildDef extracted + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef * @return HTMLPurifier_ChildDef corresponding to ElementDef */ - public function getChildDef($def, $module) { + public function getChildDef($def, $module) + { $value = $def->content_model; if (is_object($value)) { trigger_error( @@ -126,7 +139,9 @@ class HTMLPurifier_ContentSets if ($module->defines_child_def) { // save a func call $return = $module->getChildDef($def); } - if ($return !== false) return $return; + if ($return !== false) { + return $return; + } // error-out trigger_error( 'Could not determine which ChildDef class to instantiate', @@ -138,18 +153,18 @@ class HTMLPurifier_ContentSets /** * Converts a string list of elements separated by pipes into * a lookup array. - * @param $string List of elements - * @return Lookup array of elements + * @param string $string List of elements + * @return array Lookup array of elements */ - protected function convertToLookup($string) { + protected function convertToLookup($string) + { $array = explode('|', str_replace(' ', '', $string)); $ret = array(); - foreach ($array as $i => $k) { + foreach ($array as $k) { $ret[$k] = true; } return $ret; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Context.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Context.php new file mode 100644 index 0000000000..00e509c85c --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Context.php @@ -0,0 +1,95 @@ +<?php + +/** + * Registry object that contains information about the current context. + * @warning Is a bit buggy when variables are set to null: it thinks + * they don't exist! So use false instead, please. + * @note Since the variables Context deals with may not be objects, + * references are very important here! Do not remove! + */ +class HTMLPurifier_Context +{ + + /** + * Private array that stores the references. + * @type array + */ + private $_storage = array(); + + /** + * Registers a variable into the context. + * @param string $name String name + * @param mixed $ref Reference to variable to be registered + */ + public function register($name, &$ref) + { + if (array_key_exists($name, $this->_storage)) { + trigger_error( + "Name $name produces collision, cannot re-register", + E_USER_ERROR + ); + return; + } + $this->_storage[$name] =& $ref; + } + + /** + * Retrieves a variable reference from the context. + * @param string $name String name + * @param bool $ignore_error Boolean whether or not to ignore error + * @return mixed + */ + public function &get($name, $ignore_error = false) + { + if (!array_key_exists($name, $this->_storage)) { + if (!$ignore_error) { + trigger_error( + "Attempted to retrieve non-existent variable $name", + E_USER_ERROR + ); + } + $var = null; // so we can return by reference + return $var; + } + return $this->_storage[$name]; + } + + /** + * Destroys a variable in the context. + * @param string $name String name + */ + public function destroy($name) + { + if (!array_key_exists($name, $this->_storage)) { + trigger_error( + "Attempted to destroy non-existent variable $name", + E_USER_ERROR + ); + return; + } + unset($this->_storage[$name]); + } + + /** + * Checks whether or not the variable exists. + * @param string $name String name + * @return bool + */ + public function exists($name) + { + return array_key_exists($name, $this->_storage); + } + + /** + * Loads a series of variables from an associative array + * @param array $context_array Assoc array of variables to load + */ + public function loadArray($context_array) + { + foreach ($context_array as $key => $discard) { + $this->register($key, $context_array[$key]); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php new file mode 100644 index 0000000000..bc6d433647 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php @@ -0,0 +1,55 @@ +<?php + +/** + * Super-class for definition datatype objects, implements serialization + * functions for the class. + */ +abstract class HTMLPurifier_Definition +{ + + /** + * Has setup() been called yet? + * @type bool + */ + public $setup = false; + + /** + * If true, write out the final definition object to the cache after + * setup. This will be true only if all invocations to get a raw + * definition object are also optimized. This does not cause file + * system thrashing because on subsequent calls the cached object + * is used and any writes to the raw definition object are short + * circuited. See enduser-customize.html for the high-level + * picture. + * @type bool + */ + public $optimized = null; + + /** + * What type of definition is it? + * @type string + */ + public $type; + + /** + * Sets up the definition object into the final form, something + * not done by the constructor + * @param HTMLPurifier_Config $config + */ + abstract protected function doSetup($config); + + /** + * Setup function that aborts if already setup + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + if ($this->setup) { + return; + } + $this->setup = true; + $this->doSetup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php similarity index 66% rename from library/HTMLPurifier/DefinitionCache.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php index c6e1e388c6..67bb5b1e69 100644 --- a/library/HTMLPurifier/DefinitionCache.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php @@ -10,22 +10,27 @@ */ abstract class HTMLPurifier_DefinitionCache { - + /** + * @type string + */ public $type; /** - * @param $name Type of definition objects this instance of the + * @param string $type Type of definition objects this instance of the * cache will handle. */ - public function __construct($type) { + public function __construct($type) + { $this->type = $type; } /** * Generates a unique identifier for a particular configuration - * @param Instance of HTMLPurifier_Config + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @return string */ - public function generateKey($config) { + public function generateKey($config) + { return $config->version . ',' . // possibly replace with function calls $config->getBatchSerial($this->type) . ',' . $config->get($this->type . '.DefinitionRev'); @@ -34,30 +39,37 @@ abstract class HTMLPurifier_DefinitionCache /** * Tests whether or not a key is old with respect to the configuration's * version and revision number. - * @param $key Key to test - * @param $config Instance of HTMLPurifier_Config to test against + * @param string $key Key to test + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against + * @return bool */ - public function isOld($key, $config) { - if (substr_count($key, ',') < 2) return true; + public function isOld($key, $config) + { + if (substr_count($key, ',') < 2) { + return true; + } list($version, $hash, $revision) = explode(',', $key, 3); $compare = version_compare($version, $config->version); // version mismatch, is always old - if ($compare != 0) return true; + if ($compare != 0) { + return true; + } // versions match, ids match, check revision number - if ( - $hash == $config->getBatchSerial($this->type) && - $revision < $config->get($this->type . '.DefinitionRev') - ) return true; + if ($hash == $config->getBatchSerial($this->type) && + $revision < $config->get($this->type . '.DefinitionRev')) { + return true; + } return false; } /** * Checks if a definition's type jives with the cache's type * @note Throws an error on failure - * @param $def Definition object to check - * @return Boolean true if good, false if not + * @param HTMLPurifier_Definition $def Definition object to check + * @return bool true if good, false if not */ - public function checkDefType($def) { + public function checkDefType($def) + { if ($def->type !== $this->type) { trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}"); return false; @@ -67,31 +79,40 @@ abstract class HTMLPurifier_DefinitionCache /** * Adds a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config */ abstract public function add($def, $config); /** * Unconditionally saves a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config */ abstract public function set($def, $config); /** * Replace an object in the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config */ abstract public function replace($def, $config); /** * Retrieves a definition object from the cache + * @param HTMLPurifier_Config $config */ abstract public function get($config); /** * Removes a definition object to the cache + * @param HTMLPurifier_Config $config */ abstract public function remove($config); /** * Clears all objects from cache + * @param HTMLPurifier_Config $config */ abstract public function flush($config); @@ -100,9 +121,9 @@ abstract class HTMLPurifier_DefinitionCache * @note Be carefuly implementing this method as flush. Flush must * not interfere with other Definition types, and cleanup() * should not be repeatedly called by userland code. + * @param HTMLPurifier_Config $config */ abstract public function cleanup($config); - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php new file mode 100644 index 0000000000..b57a51b6cb --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php @@ -0,0 +1,112 @@ +<?php + +class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache +{ + + /** + * Cache object we are decorating + * @type HTMLPurifier_DefinitionCache + */ + public $cache; + + /** + * The name of the decorator + * @var string + */ + public $name; + + public function __construct() + { + } + + /** + * Lazy decorator function + * @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate + * @return HTMLPurifier_DefinitionCache_Decorator + */ + public function decorate(&$cache) + { + $decorator = $this->copy(); + // reference is necessary for mocks in PHP 4 + $decorator->cache =& $cache; + $decorator->type = $cache->type; + return $decorator; + } + + /** + * Cross-compatible clone substitute + * @return HTMLPurifier_DefinitionCache_Decorator + */ + public function copy() + { + return new HTMLPurifier_DefinitionCache_Decorator(); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function add($def, $config) + { + return $this->cache->add($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + return $this->cache->set($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + return $this->cache->replace($def, $config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + return $this->cache->get($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function remove($config) + { + return $this->cache->remove($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function flush($config) + { + return $this->cache->flush($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function cleanup($config) + { + return $this->cache->cleanup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php new file mode 100644 index 0000000000..4991777ce1 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php @@ -0,0 +1,78 @@ +<?php + +/** + * Definition cache decorator class that cleans up the cache + * whenever there is a cache miss. + */ +class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator +{ + /** + * @type string + */ + public $name = 'Cleanup'; + + /** + * @return HTMLPurifier_DefinitionCache_Decorator_Cleanup + */ + public function copy() + { + return new HTMLPurifier_DefinitionCache_Decorator_Cleanup(); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function add($def, $config) + { + $status = parent::add($def, $config); + if (!$status) { + parent::cleanup($config); + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + $status = parent::set($def, $config); + if (!$status) { + parent::cleanup($config); + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + $status = parent::replace($def, $config); + if (!$status) { + parent::cleanup($config); + } + return $status; + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + $ret = parent::get($config); + if (!$ret) { + parent::cleanup($config); + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php new file mode 100644 index 0000000000..d529dce48d --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Memory.php @@ -0,0 +1,85 @@ +<?php + +/** + * Definition cache decorator class that saves all cache retrievals + * to PHP's memory; good for unit tests or circumstances where + * there are lots of configuration objects floating around. + */ +class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator +{ + /** + * @type array + */ + protected $definitions; + + /** + * @type string + */ + public $name = 'Memory'; + + /** + * @return HTMLPurifier_DefinitionCache_Decorator_Memory + */ + public function copy() + { + return new HTMLPurifier_DefinitionCache_Decorator_Memory(); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function add($def, $config) + { + $status = parent::add($def, $config); + if ($status) { + $this->definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + $status = parent::set($def, $config); + if ($status) { + $this->definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + $status = parent::replace($def, $config); + if ($status) { + $this->definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + $key = $this->generateKey($config); + if (isset($this->definitions[$key])) { + return $this->definitions[$key]; + } + $this->definitions[$key] = parent::get($config); + return $this->definitions[$key]; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in new file mode 100644 index 0000000000..b1fec8d367 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in @@ -0,0 +1,82 @@ +<?php + +require_once 'HTMLPurifier/DefinitionCache/Decorator.php'; + +/** + * Definition cache decorator template. + */ +class HTMLPurifier_DefinitionCache_Decorator_Template extends HTMLPurifier_DefinitionCache_Decorator +{ + + /** + * @type string + */ + public $name = 'Template'; // replace this + + public function copy() + { + // replace class name with yours + return new HTMLPurifier_DefinitionCache_Decorator_Template(); + } + + // remove methods you don't need + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function add($def, $config) + { + return parent::add($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + return parent::set($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + return parent::replace($def, $config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + return parent::get($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function flush($config) + { + return parent::flush($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function cleanup($config) + { + return parent::cleanup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php new file mode 100644 index 0000000000..d9a75ce227 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Null.php @@ -0,0 +1,76 @@ +<?php + +/** + * Null cache object to use when no caching is on. + */ +class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache +{ + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return bool + */ + public function add($def, $config) + { + return false; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return bool + */ + public function set($def, $config) + { + return false; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return bool + */ + public function replace($def, $config) + { + return false; + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function remove($config) + { + return false; + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function get($config) + { + return false; + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function flush($config) + { + return false; + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function cleanup($config) + { + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php new file mode 100644 index 0000000000..ce268d91b4 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer.php @@ -0,0 +1,291 @@ +<?php + +class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache +{ + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return int|bool + */ + public function add($def, $config) + { + if (!$this->checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (file_exists($file)) { + return false; + } + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return int|bool + */ + public function set($def, $config) + { + if (!$this->checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return int|bool + */ + public function replace($def, $config) + { + if (!$this->checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool|HTMLPurifier_Config + */ + public function get($config) + { + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + return unserialize(file_get_contents($file)); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function remove($config) + { + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + return unlink($file); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function flush($config) + { + if (!$this->_prepareDir($config)) { + return false; + } + $dir = $this->generateDirectoryPath($config); + $dh = opendir($dir); + while (false !== ($filename = readdir($dh))) { + if (empty($filename)) { + continue; + } + if ($filename[0] === '.') { + continue; + } + unlink($dir . '/' . $filename); + } + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function cleanup($config) + { + if (!$this->_prepareDir($config)) { + return false; + } + $dir = $this->generateDirectoryPath($config); + $dh = opendir($dir); + while (false !== ($filename = readdir($dh))) { + if (empty($filename)) { + continue; + } + if ($filename[0] === '.') { + continue; + } + $key = substr($filename, 0, strlen($filename) - 4); + if ($this->isOld($key, $config)) { + unlink($dir . '/' . $filename); + } + } + } + + /** + * Generates the file path to the serial file corresponding to + * the configuration and definition name + * @param HTMLPurifier_Config $config + * @return string + * @todo Make protected + */ + public function generateFilePath($config) + { + $key = $this->generateKey($config); + return $this->generateDirectoryPath($config) . '/' . $key . '.ser'; + } + + /** + * Generates the path to the directory contain this cache's serial files + * @param HTMLPurifier_Config $config + * @return string + * @note No trailing slash + * @todo Make protected + */ + public function generateDirectoryPath($config) + { + $base = $this->generateBaseDirectoryPath($config); + return $base . '/' . $this->type; + } + + /** + * Generates path to base directory that contains all definition type + * serials + * @param HTMLPurifier_Config $config + * @return mixed|string + * @todo Make protected + */ + public function generateBaseDirectoryPath($config) + { + $base = $config->get('Cache.SerializerPath'); + $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base; + return $base; + } + + /** + * Convenience wrapper function for file_put_contents + * @param string $file File name to write to + * @param string $data Data to write into file + * @param HTMLPurifier_Config $config + * @return int|bool Number of bytes written if success, or false if failure. + */ + private function _write($file, $data, $config) + { + $result = file_put_contents($file, $data); + if ($result !== false) { + // set permissions of the new file (no execute) + $chmod = $config->get('Cache.SerializerPermissions'); + if (!$chmod) { + $chmod = 0644; // invalid config or simpletest + } + $chmod = $chmod & 0666; + chmod($file, $chmod); + } + return $result; + } + + /** + * Prepares the directory that this type stores the serials in + * @param HTMLPurifier_Config $config + * @return bool True if successful + */ + private function _prepareDir($config) + { + $directory = $this->generateDirectoryPath($config); + $chmod = $config->get('Cache.SerializerPermissions'); + if (!$chmod) { + $chmod = 0755; // invalid config or simpletest + } + if (!is_dir($directory)) { + $base = $this->generateBaseDirectoryPath($config); + if (!is_dir($base)) { + trigger_error( + 'Base directory ' . $base . ' does not exist, + please create or change using %Cache.SerializerPath', + E_USER_WARNING + ); + return false; + } elseif (!$this->_testPermissions($base, $chmod)) { + return false; + } + mkdir($directory, $chmod); + if (!$this->_testPermissions($directory, $chmod)) { + trigger_error( + 'Base directory ' . $base . ' does not exist, + please create or change using %Cache.SerializerPath', + E_USER_WARNING + ); + return false; + } + } elseif (!$this->_testPermissions($directory, $chmod)) { + return false; + } + return true; + } + + /** + * Tests permissions on a directory and throws out friendly + * error messages and attempts to chmod it itself if possible + * @param string $dir Directory path + * @param int $chmod Permissions + * @return bool True if directory is writable + */ + private function _testPermissions($dir, $chmod) + { + // early abort, if it is writable, everything is hunky-dory + if (is_writable($dir)) { + return true; + } + if (!is_dir($dir)) { + // generally, you'll want to handle this beforehand + // so a more specific error message can be given + trigger_error( + 'Directory ' . $dir . ' does not exist', + E_USER_WARNING + ); + return false; + } + if (function_exists('posix_getuid')) { + // POSIX system, we can give more specific advice + if (fileowner($dir) === posix_getuid()) { + // we can chmod it ourselves + $chmod = $chmod | 0700; + if (chmod($dir, $chmod)) { + return true; + } + } elseif (filegroup($dir) === posix_getgid()) { + $chmod = $chmod | 0070; + } else { + // PHP's probably running as nobody, so we'll + // need to give global permissions + $chmod = $chmod | 0777; + } + trigger_error( + 'Directory ' . $dir . ' not writable, ' . + 'please chmod to ' . decoct($chmod), + E_USER_WARNING + ); + } else { + // generic error message + trigger_error( + 'Directory ' . $dir . ' not writable, ' . + 'please alter file permissions', + E_USER_WARNING + ); + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/DefinitionCache/Serializer/README b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README similarity index 100% rename from library/HTMLPurifier/DefinitionCache/Serializer/README rename to library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README diff --git a/library/HTMLPurifier/DefinitionCacheFactory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php similarity index 67% rename from library/HTMLPurifier/DefinitionCacheFactory.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php index a6ead62818..fd1cc9be46 100644 --- a/library/HTMLPurifier/DefinitionCacheFactory.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php @@ -5,22 +5,36 @@ */ class HTMLPurifier_DefinitionCacheFactory { - + /** + * @type array + */ protected $caches = array('Serializer' => array()); + + /** + * @type array + */ protected $implementations = array(); + + /** + * @type HTMLPurifier_DefinitionCache_Decorator[] + */ protected $decorators = array(); /** * Initialize default decorators */ - public function setup() { + public function setup() + { $this->addDecorator('Cleanup'); } /** * Retrieves an instance of global definition cache factory. + * @param HTMLPurifier_DefinitionCacheFactory $prototype + * @return HTMLPurifier_DefinitionCacheFactory */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { static $instance; if ($prototype !== null) { $instance = $prototype; @@ -33,19 +47,22 @@ class HTMLPurifier_DefinitionCacheFactory /** * Registers a new definition cache object - * @param $short Short name of cache object, for reference - * @param $long Full class name of cache object, for construction + * @param string $short Short name of cache object, for reference + * @param string $long Full class name of cache object, for construction */ - public function register($short, $long) { + public function register($short, $long) + { $this->implementations[$short] = $long; } /** * Factory method that creates a cache object based on configuration - * @param $name Name of definitions handled by cache - * @param $config Instance of HTMLPurifier_Config + * @param string $type Name of definitions handled by cache + * @param HTMLPurifier_Config $config Config instance + * @return mixed */ - public function create($type, $config) { + public function create($type, $config) + { $method = $config->get('Cache.DefinitionImpl'); if ($method === null) { return new HTMLPurifier_DefinitionCache_Null($type); @@ -53,10 +70,8 @@ class HTMLPurifier_DefinitionCacheFactory if (!empty($this->caches[$method][$type])) { return $this->caches[$method][$type]; } - if ( - isset($this->implementations[$method]) && - class_exists($class = $this->implementations[$method], false) - ) { + if (isset($this->implementations[$method]) && + class_exists($class = $this->implementations[$method], false)) { $cache = new $class($type); } else { if ($method != 'Serializer') { @@ -76,16 +91,16 @@ class HTMLPurifier_DefinitionCacheFactory /** * Registers a decorator to add to all new cache objects - * @param + * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator */ - public function addDecorator($decorator) { + public function addDecorator($decorator) + { if (is_string($decorator)) { $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator"; $decorator = new $class; } $this->decorators[$decorator->name] = $decorator; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Doctype.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php similarity index 77% rename from library/HTMLPurifier/Doctype.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php index 1e3c574c06..4acd06e5bd 100644 --- a/library/HTMLPurifier/Doctype.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php @@ -10,42 +10,55 @@ class HTMLPurifier_Doctype { /** * Full name of doctype + * @type string */ public $name; /** * List of standard modules (string identifiers or literal objects) * that this doctype uses + * @type array */ public $modules = array(); /** * List of modules to use for tidying up code + * @type array */ public $tidyModules = array(); /** * Is the language derived from XML (i.e. XHTML)? + * @type bool */ public $xml = true; /** * List of aliases for this doctype + * @type array */ public $aliases = array(); /** * Public DTD identifier + * @type string */ public $dtdPublic; /** * System DTD identifier + * @type string */ public $dtdSystem; - public function __construct($name = null, $xml = true, $modules = array(), - $tidyModules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null + public function __construct( + $name = null, + $xml = true, + $modules = array(), + $tidyModules = array(), + $aliases = array(), + $dtd_public = null, + $dtd_system = null ) { $this->name = $name; $this->xml = $xml; diff --git a/library/HTMLPurifier/DoctypeRegistry.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php similarity index 51% rename from library/HTMLPurifier/DoctypeRegistry.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php index 86049e9391..acc1d64a62 100644 --- a/library/HTMLPurifier/DoctypeRegistry.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php @@ -4,12 +4,14 @@ class HTMLPurifier_DoctypeRegistry { /** - * Hash of doctype names to doctype objects + * Hash of doctype names to doctype objects. + * @type array */ protected $doctypes; /** - * Lookup table of aliases to real doctype names + * Lookup table of aliases to real doctype names. + * @type array */ protected $aliases; @@ -17,32 +19,57 @@ class HTMLPurifier_DoctypeRegistry * Registers a doctype to the registry * @note Accepts a fully-formed doctype object, or the * parameters for constructing a doctype object - * @param $doctype Name of doctype or literal doctype object - * @param $modules Modules doctype will load - * @param $modules_for_modes Modules doctype will load for certain modes - * @param $aliases Alias names for doctype - * @return Editable registered doctype + * @param string $doctype Name of doctype or literal doctype object + * @param bool $xml + * @param array $modules Modules doctype will load + * @param array $tidy_modules Modules doctype will load for certain modes + * @param array $aliases Alias names for doctype + * @param string $dtd_public + * @param string $dtd_system + * @return HTMLPurifier_Doctype Editable registered doctype */ - public function register($doctype, $xml = true, $modules = array(), - $tidy_modules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null + public function register( + $doctype, + $xml = true, + $modules = array(), + $tidy_modules = array(), + $aliases = array(), + $dtd_public = null, + $dtd_system = null ) { - if (!is_array($modules)) $modules = array($modules); - if (!is_array($tidy_modules)) $tidy_modules = array($tidy_modules); - if (!is_array($aliases)) $aliases = array($aliases); + if (!is_array($modules)) { + $modules = array($modules); + } + if (!is_array($tidy_modules)) { + $tidy_modules = array($tidy_modules); + } + if (!is_array($aliases)) { + $aliases = array($aliases); + } if (!is_object($doctype)) { $doctype = new HTMLPurifier_Doctype( - $doctype, $xml, $modules, $tidy_modules, $aliases, $dtd_public, $dtd_system + $doctype, + $xml, + $modules, + $tidy_modules, + $aliases, + $dtd_public, + $dtd_system ); } $this->doctypes[$doctype->name] = $doctype; $name = $doctype->name; // hookup aliases foreach ($doctype->aliases as $alias) { - if (isset($this->doctypes[$alias])) continue; + if (isset($this->doctypes[$alias])) { + continue; + } $this->aliases[$alias] = $name; } // remove old aliases - if (isset($this->aliases[$name])) unset($this->aliases[$name]); + if (isset($this->aliases[$name])) { + unset($this->aliases[$name]); + } return $doctype; } @@ -50,11 +77,14 @@ class HTMLPurifier_DoctypeRegistry * Retrieves reference to a doctype of a certain name * @note This function resolves aliases * @note When possible, use the more fully-featured make() - * @param $doctype Name of doctype - * @return Editable doctype object + * @param string $doctype Name of doctype + * @return HTMLPurifier_Doctype Editable doctype object */ - public function get($doctype) { - if (isset($this->aliases[$doctype])) $doctype = $this->aliases[$doctype]; + public function get($doctype) + { + if (isset($this->aliases[$doctype])) { + $doctype = $this->aliases[$doctype]; + } if (!isset($this->doctypes[$doctype])) { trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR); $anon = new HTMLPurifier_Doctype($doctype); @@ -70,20 +100,30 @@ class HTMLPurifier_DoctypeRegistry * can hold on to (this is necessary in order to tell * Generator whether or not the current document is XML * based or not). + * @param HTMLPurifier_Config $config + * @return HTMLPurifier_Doctype */ - public function make($config) { + public function make($config) + { return clone $this->get($this->getDoctypeFromConfig($config)); } /** * Retrieves the doctype from the configuration object + * @param HTMLPurifier_Config $config + * @return string */ - public function getDoctypeFromConfig($config) { + public function getDoctypeFromConfig($config) + { // recommended test $doctype = $config->get('HTML.Doctype'); - if (!empty($doctype)) return $doctype; + if (!empty($doctype)) { + return $doctype; + } $doctype = $config->get('HTML.CustomDoctype'); - if (!empty($doctype)) return $doctype; + if (!empty($doctype)) { + return $doctype; + } // backwards-compatibility if ($config->get('HTML.XHTML')) { $doctype = 'XHTML 1.0'; @@ -97,7 +137,6 @@ class HTMLPurifier_DoctypeRegistry } return $doctype; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ElementDef.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php similarity index 69% rename from library/HTMLPurifier/ElementDef.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php index 5498d95670..d5311cedcf 100644 --- a/library/HTMLPurifier/ElementDef.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php @@ -10,15 +10,16 @@ */ class HTMLPurifier_ElementDef { - /** * Does the definition work by itself, or is it created solely * for the purpose of merging into another definition? + * @type bool */ public $standalone = true; /** - * Associative array of attribute name to HTMLPurifier_AttrDef + * Associative array of attribute name to HTMLPurifier_AttrDef. + * @type array * @note Before being processed by HTMLPurifier_AttrCollections * when modules are finalized during * HTMLPurifier_HTMLDefinition->setup(), this array may also @@ -30,27 +31,43 @@ class HTMLPurifier_ElementDef */ public $attr = array(); + // XXX: Design note: currently, it's not possible to override + // previously defined AttrTransforms without messing around with + // the final generated config. This is by design; a previous version + // used an associated list of attr_transform, but it was extremely + // easy to accidentally override other attribute transforms by + // forgetting to specify an index (and just using 0.) While we + // could check this by checking the index number and complaining, + // there is a second problem which is that it is not at all easy to + // tell when something is getting overridden. Combine this with a + // codebase where this isn't really being used, and it's perfect for + // nuking. + /** - * Indexed list of tag's HTMLPurifier_AttrTransform to be done before validation + * List of tags HTMLPurifier_AttrTransform to be done before validation. + * @type array */ public $attr_transform_pre = array(); /** - * Indexed list of tag's HTMLPurifier_AttrTransform to be done after validation + * List of tags HTMLPurifier_AttrTransform to be done after validation. + * @type array */ public $attr_transform_post = array(); /** * HTMLPurifier_ChildDef of this tag. + * @type HTMLPurifier_ChildDef */ public $child; /** - * Abstract string representation of internal ChildDef rules. See - * HTMLPurifier_ContentSets for how this is parsed and then transformed + * Abstract string representation of internal ChildDef rules. + * @see HTMLPurifier_ContentSets for how this is parsed and then transformed * into an HTMLPurifier_ChildDef. * @warning This is a temporary variable that is not available after * being processed by HTMLDefinition + * @type string */ public $content_model; @@ -60,27 +77,29 @@ class HTMLPurifier_ElementDef * @warning This must be lowercase * @warning This is a temporary variable that is not available after * being processed by HTMLDefinition + * @type string */ public $content_model_type; - - /** * Does the element have a content model (#PCDATA | Inline)*? This * is important for chameleon ins and del processing in * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't * have to worry about this one. + * @type bool */ public $descendants_are_inline = false; /** - * List of the names of required attributes this element has. Dynamically - * populated by HTMLPurifier_HTMLDefinition::getElement + * List of the names of required attributes this element has. + * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement() + * @type array */ public $required_attr = array(); /** * Lookup table of tags excluded from all descendants of this tag. + * @type array * @note SGML permits exclusions for all descendants, but this is * not possible with DTDs or XML Schemas. W3C has elected to * use complicated compositions of content_models to simulate @@ -94,6 +113,7 @@ class HTMLPurifier_ElementDef /** * This tag is explicitly auto-closed by the following tags. + * @type array */ public $autoclose = array(); @@ -101,19 +121,22 @@ class HTMLPurifier_ElementDef * If a foreign element is found in this element, test if it is * allowed by this sub-element; if it is, instead of closing the * current element, place it inside this element. + * @type string */ public $wrap; /** * Whether or not this is a formatting element affected by the * "Active Formatting Elements" algorithm. + * @type bool */ public $formatting; /** * Low-level factory constructor for creating new standalone element defs */ - public static function create($content_model, $content_model_type, $attr) { + public static function create($content_model, $content_model_type, $attr) + { $def = new HTMLPurifier_ElementDef(); $def->content_model = $content_model; $def->content_model_type = $content_model_type; @@ -125,11 +148,12 @@ class HTMLPurifier_ElementDef * Merges the values of another element definition into this one. * Values from the new element def take precedence if a value is * not mergeable. + * @param HTMLPurifier_ElementDef $def */ - public function mergeIn($def) { - + public function mergeIn($def) + { // later keys takes precedence - foreach($def->attr as $k => $v) { + foreach ($def->attr as $k => $v) { if ($k === 0) { // merge in the includes // sorry, no way to override an include @@ -139,28 +163,35 @@ class HTMLPurifier_ElementDef continue; } if ($v === false) { - if (isset($this->attr[$k])) unset($this->attr[$k]); + if (isset($this->attr[$k])) { + unset($this->attr[$k]); + } continue; } $this->attr[$k] = $v; } - $this->_mergeAssocArray($this->attr_transform_pre, $def->attr_transform_pre); - $this->_mergeAssocArray($this->attr_transform_post, $def->attr_transform_post); $this->_mergeAssocArray($this->excludes, $def->excludes); + $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre); + $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post); - if(!empty($def->content_model)) { + if (!empty($def->content_model)) { $this->content_model = str_replace("#SUPER", $this->content_model, $def->content_model); $this->child = false; } - if(!empty($def->content_model_type)) { + if (!empty($def->content_model_type)) { $this->content_model_type = $def->content_model_type; $this->child = false; } - if(!is_null($def->child)) $this->child = $def->child; - if(!is_null($def->formatting)) $this->formatting = $def->formatting; - if($def->descendants_are_inline) $this->descendants_are_inline = $def->descendants_are_inline; - + if (!is_null($def->child)) { + $this->child = $def->child; + } + if (!is_null($def->formatting)) { + $this->formatting = $def->formatting; + } + if ($def->descendants_are_inline) { + $this->descendants_are_inline = $def->descendants_are_inline; + } } /** @@ -168,16 +199,18 @@ class HTMLPurifier_ElementDef * @param $a1 Array by reference that is merged into * @param $a2 Array that merges into $a1 */ - private function _mergeAssocArray(&$a1, $a2) { + private function _mergeAssocArray(&$a1, $a2) + { foreach ($a2 as $k => $v) { if ($v === false) { - if (isset($a1[$k])) unset($a1[$k]); + if (isset($a1[$k])) { + unset($a1[$k]); + } continue; } $a1[$k] = $v; } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Encoder.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php similarity index 63% rename from library/HTMLPurifier/Encoder.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php index 2b3140caaf..fef9b58906 100644 --- a/library/HTMLPurifier/Encoder.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php @@ -10,14 +10,90 @@ class HTMLPurifier_Encoder /** * Constructor throws fatal error if you attempt to instantiate class */ - private function __construct() { + private function __construct() + { trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR); } /** * Error-handler that mutes errors, alternative to shut-up operator. */ - public static function muteErrorHandler() {} + public static function muteErrorHandler() + { + } + + /** + * iconv wrapper which mutes errors, but doesn't work around bugs. + * @param string $in Input encoding + * @param string $out Output encoding + * @param string $text The text to convert + * @return string + */ + public static function unsafeIconv($in, $out, $text) + { + set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); + $r = iconv($in, $out, $text); + restore_error_handler(); + return $r; + } + + /** + * iconv wrapper which mutes errors and works around bugs. + * @param string $in Input encoding + * @param string $out Output encoding + * @param string $text The text to convert + * @param int $max_chunk_size + * @return string + */ + public static function iconv($in, $out, $text, $max_chunk_size = 8000) + { + $code = self::testIconvTruncateBug(); + if ($code == self::ICONV_OK) { + return self::unsafeIconv($in, $out, $text); + } elseif ($code == self::ICONV_TRUNCATES) { + // we can only work around this if the input character set + // is utf-8 + if ($in == 'utf-8') { + if ($max_chunk_size < 4) { + trigger_error('max_chunk_size is too small', E_USER_WARNING); + return false; + } + // split into 8000 byte chunks, but be careful to handle + // multibyte boundaries properly + if (($c = strlen($text)) <= $max_chunk_size) { + return self::unsafeIconv($in, $out, $text); + } + $r = ''; + $i = 0; + while (true) { + if ($i + $max_chunk_size >= $c) { + $r .= self::unsafeIconv($in, $out, substr($text, $i)); + break; + } + // wibble the boundary + if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) { + $chunk_size = $max_chunk_size; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) { + $chunk_size = $max_chunk_size - 1; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) { + $chunk_size = $max_chunk_size - 2; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) { + $chunk_size = $max_chunk_size - 3; + } else { + return false; // rather confusing UTF-8... + } + $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths + $r .= self::unsafeIconv($in, $out, $chunk); + $i += $chunk_size; + } + return $r; + } else { + return false; + } + } else { + return false; + } + } /** * Cleans a UTF-8 string for well-formedness and SGML validity @@ -25,6 +101,10 @@ class HTMLPurifier_Encoder * It will parse according to UTF-8 and return a valid UTF8 string, with * non-SGML codepoints excluded. * + * @param string $str The string to clean + * @param bool $force_php + * @return string + * * @note Just for reference, the non-SGML code points are 0 to 31 and * 127 to 159, inclusive. However, we allow code points 9, 10 * and 13, which are the tab, line feed and carriage return @@ -44,14 +124,17 @@ class HTMLPurifier_Encoder * would need that, and I'm probably not going to implement them. * Once again, PHP 6 should solve all our problems. */ - public static function cleanUTF8($str, $force_php = false) { - + public static function cleanUTF8($str, $force_php = false) + { // UTF-8 validity is checked since PHP 4.3.5 // This is an optimization: if the string is already valid UTF-8, no // need to do PHP stuff. 99% of the time, this will be the case. // The regexp matches the XML char production, as well as well as excluding // non-SGML codepoints U+007F to U+009F - if (preg_match('/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', $str)) { + if (preg_match( + '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', + $str + )) { return $str; } @@ -70,7 +153,7 @@ class HTMLPurifier_Encoder $char = ''; $len = strlen($str); - for($i = 0; $i < $len; $i++) { + for ($i = 0; $i < $len; $i++) { $in = ord($str{$i}); $char .= $str[$i]; // append byte to char if (0 == $mState) { @@ -223,8 +306,9 @@ class HTMLPurifier_Encoder // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes // +----------+----------+----------+----------+ - public static function unichr($code) { - if($code > 1114111 or $code < 0 or + public static function unichr($code) + { + if ($code > 1114111 or $code < 0 or ($code >= 55296 and $code <= 57343) ) { // bits are set outside the "valid" range as defined // by UNICODE 4.1.0 @@ -242,7 +326,7 @@ class HTMLPurifier_Encoder $y = (($code & 2047) >> 6) | 192; } else { $y = (($code & 4032) >> 6) | 128; - if($code < 65536) { + if ($code < 65536) { $z = (($code >> 12) & 15) | 224; } else { $z = (($code >> 12) & 63) | 128; @@ -252,84 +336,129 @@ class HTMLPurifier_Encoder } // set up the actual character $ret = ''; - if($w) $ret .= chr($w); - if($z) $ret .= chr($z); - if($y) $ret .= chr($y); + if ($w) { + $ret .= chr($w); + } + if ($z) { + $ret .= chr($z); + } + if ($y) { + $ret .= chr($y); + } $ret .= chr($x); return $ret; } /** - * Converts a string to UTF-8 based on configuration. + * @return bool */ - public static function convertToUTF8($str, $config, $context) { - $encoding = $config->get('Core.Encoding'); - if ($encoding === 'utf-8') return $str; + public static function iconvAvailable() + { static $iconv = null; - if ($iconv === null) $iconv = function_exists('iconv'); - set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); + if ($iconv === null) { + $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE; + } + return $iconv; + } + + /** + * Convert a string to UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public static function convertToUTF8($str, $config, $context) + { + $encoding = $config->get('Core.Encoding'); + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + if ($iconv === null) { + $iconv = self::iconvAvailable(); + } if ($iconv && !$config->get('Test.ForceNoIconv')) { - $str = iconv($encoding, 'utf-8//IGNORE', $str); + // unaffected by bugs, since UTF-8 support all characters + $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str); if ($str === false) { // $encoding is not a valid encoding - restore_error_handler(); trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR); return ''; } // If the string is bjorked by Shift_JIS or a similar encoding // that doesn't support all of ASCII, convert the naughty // characters to their true byte-wise ASCII/UTF-8 equivalents. - $str = strtr($str, HTMLPurifier_Encoder::testEncodingSupportsASCII($encoding)); - restore_error_handler(); + $str = strtr($str, self::testEncodingSupportsASCII($encoding)); return $str; } elseif ($encoding === 'iso-8859-1') { $str = utf8_encode($str); - restore_error_handler(); return $str; } - trigger_error('Encoding not supported, please install iconv', E_USER_ERROR); + $bug = HTMLPurifier_Encoder::testIconvTruncateBug(); + if ($bug == self::ICONV_OK) { + trigger_error('Encoding not supported, please install iconv', E_USER_ERROR); + } else { + trigger_error( + 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' . + 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541', + E_USER_ERROR + ); + } } /** * Converts a string from UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string * @note Currently, this is a lossy conversion, with unexpressable * characters being omitted. */ - public static function convertFromUTF8($str, $config, $context) { + public static function convertFromUTF8($str, $config, $context) + { $encoding = $config->get('Core.Encoding'); - if ($encoding === 'utf-8') return $str; - static $iconv = null; - if ($iconv === null) $iconv = function_exists('iconv'); if ($escape = $config->get('Core.EscapeNonASCIICharacters')) { - $str = HTMLPurifier_Encoder::convertToASCIIDumbLossless($str); + $str = self::convertToASCIIDumbLossless($str); + } + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + if ($iconv === null) { + $iconv = self::iconvAvailable(); } - set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); if ($iconv && !$config->get('Test.ForceNoIconv')) { // Undo our previous fix in convertToUTF8, otherwise iconv will barf - $ascii_fix = HTMLPurifier_Encoder::testEncodingSupportsASCII($encoding); + $ascii_fix = self::testEncodingSupportsASCII($encoding); if (!$escape && !empty($ascii_fix)) { $clear_fix = array(); - foreach ($ascii_fix as $utf8 => $native) $clear_fix[$utf8] = ''; + foreach ($ascii_fix as $utf8 => $native) { + $clear_fix[$utf8] = ''; + } $str = strtr($str, $clear_fix); } $str = strtr($str, array_flip($ascii_fix)); // Normal stuff - $str = iconv('utf-8', $encoding . '//IGNORE', $str); - restore_error_handler(); + $str = self::iconv('utf-8', $encoding . '//IGNORE', $str); return $str; } elseif ($encoding === 'iso-8859-1') { $str = utf8_decode($str); - restore_error_handler(); return $str; } trigger_error('Encoding not supported', E_USER_ERROR); + // You might be tempted to assume that the ASCII representation + // might be OK, however, this is *not* universally true over all + // encodings. So we take the conservative route here, rather + // than forcibly turn on %Core.EscapeNonASCIICharacters } /** * Lossless (character-wise) conversion of HTML to ASCII - * @param $str UTF-8 string to be converted to ASCII - * @returns ASCII encoded string with non-ASCII character entity-ized + * @param string $str UTF-8 string to be converted to ASCII + * @return string ASCII encoded string with non-ASCII character entity-ized * @warning Adapted from MediaWiki, claiming fair use: this is a common * algorithm. If you disagree with this license fudgery, * implement it yourself. @@ -342,27 +471,28 @@ class HTMLPurifier_Encoder * @note Sort of with cleanUTF8() but it assumes that $str is * well-formed UTF-8 */ - public static function convertToASCIIDumbLossless($str) { + public static function convertToASCIIDumbLossless($str) + { $bytesleft = 0; $result = ''; $working = 0; $len = strlen($str); - for( $i = 0; $i < $len; $i++ ) { - $bytevalue = ord( $str[$i] ); - if( $bytevalue <= 0x7F ) { //0xxx xxxx - $result .= chr( $bytevalue ); + for ($i = 0; $i < $len; $i++) { + $bytevalue = ord($str[$i]); + if ($bytevalue <= 0x7F) { //0xxx xxxx + $result .= chr($bytevalue); $bytesleft = 0; - } elseif( $bytevalue <= 0xBF ) { //10xx xxxx + } elseif ($bytevalue <= 0xBF) { //10xx xxxx $working = $working << 6; $working += ($bytevalue & 0x3F); $bytesleft--; - if( $bytesleft <= 0 ) { + if ($bytesleft <= 0) { $result .= "&#" . $working . ";"; } - } elseif( $bytevalue <= 0xDF ) { //110x xxxx + } elseif ($bytevalue <= 0xDF) { //110x xxxx $working = $bytevalue & 0x1F; $bytesleft = 1; - } elseif( $bytevalue <= 0xEF ) { //1110 xxxx + } elseif ($bytevalue <= 0xEF) { //1110 xxxx $working = $bytevalue & 0x0F; $bytesleft = 2; } else { //1111 0xxx @@ -373,6 +503,54 @@ class HTMLPurifier_Encoder return $result; } + /** No bugs detected in iconv. */ + const ICONV_OK = 0; + + /** Iconv truncates output if converting from UTF-8 to another + * character set with //IGNORE, and a non-encodable character is found */ + const ICONV_TRUNCATES = 1; + + /** Iconv does not support //IGNORE, making it unusable for + * transcoding purposes */ + const ICONV_UNUSABLE = 2; + + /** + * glibc iconv has a known bug where it doesn't handle the magic + * //IGNORE stanza correctly. In particular, rather than ignore + * characters, it will return an EILSEQ after consuming some number + * of characters, and expect you to restart iconv as if it were + * an E2BIG. Old versions of PHP did not respect the errno, and + * returned the fragment, so as a result you would see iconv + * mysteriously truncating output. We can work around this by + * manually chopping our input into segments of about 8000 + * characters, as long as PHP ignores the error code. If PHP starts + * paying attention to the error code, iconv becomes unusable. + * + * @return int Error code indicating severity of bug. + */ + public static function testIconvTruncateBug() + { + static $code = null; + if ($code === null) { + // better not use iconv, otherwise infinite loop! + $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000)); + if ($r === false) { + $code = self::ICONV_UNUSABLE; + } elseif (($c = strlen($r)) < 9000) { + $code = self::ICONV_TRUNCATES; + } elseif ($c > 9000) { + trigger_error( + 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' . + 'include your iconv version as per phpversion()', + E_USER_ERROR + ); + } else { + $code = self::ICONV_OK; + } + } + return $code; + } + /** * This expensive function tests whether or not a given character * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will @@ -384,10 +562,18 @@ class HTMLPurifier_Encoder * @return Array of UTF-8 characters to their corresponding ASCII, * which can be used to "undo" any overzealous iconv action. */ - public static function testEncodingSupportsASCII($encoding, $bypass = false) { + public static function testEncodingSupportsASCII($encoding, $bypass = false) + { + // All calls to iconv here are unsafe, proof by case analysis: + // If ICONV_OK, no difference. + // If ICONV_TRUNCATE, all calls involve one character inputs, + // so bug is not triggered. + // If ICONV_UNUSABLE, this call is irrelevant static $encodings = array(); if (!$bypass) { - if (isset($encodings[$encoding])) return $encodings[$encoding]; + if (isset($encodings[$encoding])) { + return $encodings[$encoding]; + } $lenc = strtolower($encoding); switch ($lenc) { case 'shift_jis': @@ -395,32 +581,31 @@ class HTMLPurifier_Encoder case 'johab': return array("\xE2\x82\xA9" => '\\'); } - if (strpos($lenc, 'iso-8859-') === 0) return array(); + if (strpos($lenc, 'iso-8859-') === 0) { + return array(); + } } $ret = array(); - set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler')); - if (iconv('UTF-8', $encoding, 'a') === false) return false; + if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) { + return false; + } for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars $c = chr($i); // UTF-8 char - $r = iconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion - if ( - $r === '' || + $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion + if ($r === '' || // This line is needed for iconv implementations that do not // omit characters that do not exist in the target character set - ($r === $c && iconv($encoding, 'UTF-8//IGNORE', $r) !== $c) + ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c) ) { // Reverse engineer: what's the UTF-8 equiv of this byte // sequence? This assumes that there's no variable width // encoding that doesn't support ASCII. - $ret[iconv($encoding, 'UTF-8//IGNORE', $c)] = $c; + $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c; } } - restore_error_handler(); $encodings[$encoding] = $ret; return $ret; } - - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/EntityLookup.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php similarity index 75% rename from library/HTMLPurifier/EntityLookup.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php index b4dfce94c3..f12ff13a35 100644 --- a/library/HTMLPurifier/EntityLookup.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php @@ -3,20 +3,23 @@ /** * Object that provides entity lookup table from entity name to character */ -class HTMLPurifier_EntityLookup { - +class HTMLPurifier_EntityLookup +{ /** * Assoc array of entity name to character represented. + * @type array */ public $table; /** * Sets up the entity lookup table from the serialized file contents. + * @param bool $file * @note The serialized contents are versioned, but were generated * using the maintenance script generate_entity_file.php * @warning This is not in constructor to help enforce the Singleton */ - public function setup($file = false) { + public function setup($file = false) + { if (!$file) { $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser'; } @@ -25,9 +28,11 @@ class HTMLPurifier_EntityLookup { /** * Retrieves sole instance of the object. - * @param Optional prototype of custom lookup table to overload with. + * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with. + * @return HTMLPurifier_EntityLookup */ - public static function instance($prototype = false) { + public static function instance($prototype = false) + { // no references, since PHP doesn't copy unless modified static $instance = null; if ($prototype) { @@ -38,7 +43,6 @@ class HTMLPurifier_EntityLookup { } return $instance; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser new file mode 100644 index 0000000000..e8b08128be --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser @@ -0,0 +1 @@ +a:253:{s:4:"fnof";s:2:"ƒ";s:5:"Alpha";s:2:"Α";s:4:"Beta";s:2:"Β";s:5:"Gamma";s:2:"Γ";s:5:"Delta";s:2:"Δ";s:7:"Epsilon";s:2:"Ε";s:4:"Zeta";s:2:"Ζ";s:3:"Eta";s:2:"Η";s:5:"Theta";s:2:"Θ";s:4:"Iota";s:2:"Ι";s:5:"Kappa";s:2:"Κ";s:6:"Lambda";s:2:"Λ";s:2:"Mu";s:2:"Μ";s:2:"Nu";s:2:"Ν";s:2:"Xi";s:2:"Ξ";s:7:"Omicron";s:2:"Ο";s:2:"Pi";s:2:"Π";s:3:"Rho";s:2:"Ρ";s:5:"Sigma";s:2:"Σ";s:3:"Tau";s:2:"Τ";s:7:"Upsilon";s:2:"Υ";s:3:"Phi";s:2:"Φ";s:3:"Chi";s:2:"Χ";s:3:"Psi";s:2:"Ψ";s:5:"Omega";s:2:"Ω";s:5:"alpha";s:2:"α";s:4:"beta";s:2:"β";s:5:"gamma";s:2:"γ";s:5:"delta";s:2:"δ";s:7:"epsilon";s:2:"ε";s:4:"zeta";s:2:"ζ";s:3:"eta";s:2:"η";s:5:"theta";s:2:"θ";s:4:"iota";s:2:"ι";s:5:"kappa";s:2:"κ";s:6:"lambda";s:2:"λ";s:2:"mu";s:2:"μ";s:2:"nu";s:2:"ν";s:2:"xi";s:2:"ξ";s:7:"omicron";s:2:"ο";s:2:"pi";s:2:"π";s:3:"rho";s:2:"ρ";s:6:"sigmaf";s:2:"ς";s:5:"sigma";s:2:"σ";s:3:"tau";s:2:"τ";s:7:"upsilon";s:2:"υ";s:3:"phi";s:2:"φ";s:3:"chi";s:2:"χ";s:3:"psi";s:2:"ψ";s:5:"omega";s:2:"ω";s:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:6:"there4";s:3:"∴";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"";s:3:"zwj";s:3:"";s:3:"lrm";s:3:"";s:3:"rlm";s:3:"";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:4:"sup2";s:2:"²";s:4:"sup3";s:2:"³";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"sup1";s:2:"¹";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"frac14";s:2:"¼";s:6:"frac12";s:2:"½";s:6:"frac34";s:2:"¾";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";} \ No newline at end of file diff --git a/library/HTMLPurifier/EntityParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php similarity index 75% rename from library/HTMLPurifier/EntityParser.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php index 8c384472dc..61529dcd9d 100644 --- a/library/HTMLPurifier/EntityParser.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php @@ -12,19 +12,21 @@ class HTMLPurifier_EntityParser /** * Reference to entity lookup table. + * @type HTMLPurifier_EntityLookup */ protected $_entity_lookup; /** * Callback regex string for parsing entities. + * @type string */ protected $_substituteEntitiesRegex = -'/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/'; -// 1. hex 2. dec 3. string (XML style) - + '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/'; + // 1. hex 2. dec 3. string (XML style) /** * Decimal to parsed string conversion table for special entities. + * @type array */ protected $_special_dec2str = array( @@ -37,6 +39,7 @@ class HTMLPurifier_EntityParser /** * Stripped entity names to decimal conversion table for special entities. + * @type array */ protected $_special_ent2dec = array( @@ -51,41 +54,45 @@ class HTMLPurifier_EntityParser * running this whenever you have parsed character is t3h 5uck, we run * it before everything else. * - * @param $string String to have non-special entities parsed. - * @returns Parsed string. + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. */ - public function substituteNonSpecialEntities($string) { + public function substituteNonSpecialEntities($string) + { // it will try to detect missing semicolons, but don't rely on it return preg_replace_callback( $this->_substituteEntitiesRegex, array($this, 'nonSpecialEntityCallback'), $string - ); + ); } /** * Callback function for substituteNonSpecialEntities() that does the work. * - * @param $matches PCRE matches array, with 0 the entire match, and + * @param array $matches PCRE matches array, with 0 the entire match, and * either index 1, 2 or 3 set with a hex value, dec value, * or string (respectively). - * @returns Replacement string. + * @return string Replacement string. */ - protected function nonSpecialEntityCallback($matches) { + protected function nonSpecialEntityCallback($matches) + { // replaces all but big five $entity = $matches[0]; $is_num = (@$matches[0][1] === '#'); if ($is_num) { $is_hex = (@$entity[2] === 'x'); $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; - // abort for special characters - if (isset($this->_special_dec2str[$code])) return $entity; - + if (isset($this->_special_dec2str[$code])) { + return $entity; + } return HTMLPurifier_Encoder::unichr($code); } else { - if (isset($this->_special_ent2dec[$matches[3]])) return $entity; + if (isset($this->_special_ent2dec[$matches[3]])) { + return $entity; + } if (!$this->_entity_lookup) { $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); } @@ -103,14 +110,16 @@ class HTMLPurifier_EntityParser * @notice We try to avoid calling this function because otherwise, it * would have to be called a lot (for every parsed section). * - * @param $string String to have non-special entities parsed. - * @returns Parsed string. + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. */ - public function substituteSpecialEntities($string) { + public function substituteSpecialEntities($string) + { return preg_replace_callback( $this->_substituteEntitiesRegex, array($this, 'specialEntityCallback'), - $string); + $string + ); } /** @@ -118,12 +127,13 @@ class HTMLPurifier_EntityParser * * This callback has same syntax as nonSpecialEntityCallback(). * - * @param $matches PCRE-style matches array, with 0 the entire match, and + * @param array $matches PCRE-style matches array, with 0 the entire match, and * either index 1, 2 or 3 set with a hex value, dec value, * or string (respectively). - * @returns Replacement string. + * @return string Replacement string. */ - protected function specialEntityCallback($matches) { + protected function specialEntityCallback($matches) + { $entity = $matches[0]; $is_num = (@$matches[0][1] === '#'); if ($is_num) { @@ -138,7 +148,6 @@ class HTMLPurifier_EntityParser $entity; } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ErrorCollector.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php similarity index 82% rename from library/HTMLPurifier/ErrorCollector.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php index 6713eaf773..d47e3f2e24 100644 --- a/library/HTMLPurifier/ErrorCollector.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php @@ -16,16 +16,46 @@ class HTMLPurifier_ErrorCollector const MESSAGE = 2; const CHILDREN = 3; + /** + * @type array + */ protected $errors; + + /** + * @type array + */ protected $_current; + + /** + * @type array + */ protected $_stacks = array(array()); + + /** + * @type HTMLPurifier_Language + */ protected $locale; + + /** + * @type HTMLPurifier_Generator + */ protected $generator; + + /** + * @type HTMLPurifier_Context + */ protected $context; + /** + * @type array + */ protected $lines = array(); - public function __construct($context) { + /** + * @param HTMLPurifier_Context $context + */ + public function __construct($context) + { $this->locale =& $context->get('Locale'); $this->context = $context; $this->_current =& $this->_stacks[0]; @@ -34,13 +64,11 @@ class HTMLPurifier_ErrorCollector /** * Sends an error message to the collector for later use - * @param $severity int Error severity, PHP error style (don't use E_USER_) - * @param $msg string Error message text - * @param $subst1 string First substitution for $msg - * @param $subst2 string ... + * @param int $severity Error severity, PHP error style (don't use E_USER_) + * @param string $msg Error message text */ - public function send($severity, $msg) { - + public function send($severity, $msg) + { $args = array(); if (func_num_args() > 2) { $args = func_get_args(); @@ -50,7 +78,7 @@ class HTMLPurifier_ErrorCollector $token = $this->context->get('CurrentToken', true); $line = $token ? $token->line : $this->context->get('CurrentLine', true); - $col = $token ? $token->col : $this->context->get('CurrentCol', true); + $col = $token ? $token->col : $this->context->get('CurrentCol', true); $attr = $this->context->get('CurrentAttr', true); // perform special substitutions, also add custom parameters @@ -60,7 +88,9 @@ class HTMLPurifier_ErrorCollector } if (!is_null($attr)) { $subst['$CurrentAttr.Name'] = $attr; - if (isset($token->attr[$attr])) $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + if (isset($token->attr[$attr])) { + $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + } } if (empty($args)) { @@ -69,7 +99,9 @@ class HTMLPurifier_ErrorCollector $msg = $this->locale->formatMessage($msg, $args); } - if (!empty($subst)) $msg = strtr($msg, $subst); + if (!empty($subst)) { + $msg = strtr($msg, $subst); + } // (numerically indexed) $error = array( @@ -80,16 +112,15 @@ class HTMLPurifier_ErrorCollector ); $this->_current[] = $error; - // NEW CODE BELOW ... - - $struct = null; // Top-level errors are either: // TOKEN type, if $value is set appropriately, or // "syntax" type, if $value is null $new_struct = new HTMLPurifier_ErrorStruct(); $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN; - if ($token) $new_struct->value = clone $token; + if ($token) { + $new_struct->value = clone $token; + } if (is_int($line) && is_int($col)) { if (isset($this->lines[$line][$col])) { $struct = $this->lines[$line][$col]; @@ -128,30 +159,34 @@ class HTMLPurifier_ErrorCollector /** * Retrieves raw error data for custom formatter to use - * @param List of arrays in format of array(line of error, - * error severity, error message, - * recursive sub-errors array) */ - public function getRaw() { + public function getRaw() + { return $this->errors; } /** * Default HTML formatting implementation for error messages - * @param $config Configuration array, vital for HTML output nature - * @param $errors Errors array to display; used for recursion. + * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature + * @param array $errors Errors array to display; used for recursion. + * @return string */ - public function getHTMLFormatted($config, $errors = null) { + public function getHTMLFormatted($config, $errors = null) + { $ret = array(); $this->generator = new HTMLPurifier_Generator($config, $this->context); - if ($errors === null) $errors = $this->errors; + if ($errors === null) { + $errors = $this->errors; + } // 'At line' message needs to be removed // generation code for new structure goes here. It needs to be recursive. foreach ($this->lines as $line => $col_array) { - if ($line == -1) continue; + if ($line == -1) { + continue; + } foreach ($col_array as $col => $struct) { $this->_renderStruct($ret, $struct, $line, $col); } @@ -168,7 +203,8 @@ class HTMLPurifier_ErrorCollector } - private function _renderStruct(&$ret, $struct, $line = null, $col = null) { + private function _renderStruct(&$ret, $struct, $line = null, $col = null) + { $stack = array($struct); $context_stack = array(array()); while ($current = array_pop($stack)) { @@ -194,7 +230,7 @@ class HTMLPurifier_ErrorCollector //$string .= '</pre>'; $ret[] = $string; } - foreach ($current->children as $type => $array) { + foreach ($current->children as $array) { $context[] = $current; $stack = array_merge($stack, array_reverse($array, true)); for ($i = count($array); $i > 0; $i--) { @@ -203,7 +239,6 @@ class HTMLPurifier_ErrorCollector } } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ErrorStruct.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php similarity index 81% rename from library/HTMLPurifier/ErrorStruct.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php index 9bc8996ec1..cf869d3212 100644 --- a/library/HTMLPurifier/ErrorStruct.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php @@ -19,6 +19,7 @@ class HTMLPurifier_ErrorStruct /** * Type of this struct. + * @type string */ public $type; @@ -28,11 +29,13 @@ class HTMLPurifier_ErrorStruct * - TOKEN: Instance of HTMLPurifier_Token * - ATTR: array('attr-name', 'value') * - CSSPROP: array('prop-name', 'value') + * @type mixed */ public $value; /** * Errors registered for this structure. + * @type array */ public $errors = array(); @@ -40,10 +43,17 @@ class HTMLPurifier_ErrorStruct * Child ErrorStructs that are from this structure. For example, a TOKEN * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional * array in structure: [TYPE]['identifier'] + * @type array */ public $children = array(); - public function getChild($type, $id) { + /** + * @param string $type + * @param string $id + * @return mixed + */ + public function getChild($type, $id) + { if (!isset($this->children[$type][$id])) { $this->children[$type][$id] = new HTMLPurifier_ErrorStruct(); $this->children[$type][$id]->type = $type; @@ -51,10 +61,14 @@ class HTMLPurifier_ErrorStruct return $this->children[$type][$id]; } - public function addError($severity, $message) { + /** + * @param int $severity + * @param string $message + */ + public function addError($severity, $message) + { $this->errors[] = array($severity, $message); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Exception.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php similarity index 100% rename from library/HTMLPurifier/Exception.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php diff --git a/library/HTMLPurifier/Filter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php similarity index 71% rename from library/HTMLPurifier/Filter.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php index 9a0e7b09f3..c1f41ee162 100644 --- a/library/HTMLPurifier/Filter.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter.php @@ -23,24 +23,34 @@ class HTMLPurifier_Filter { /** - * Name of the filter for identification purposes + * Name of the filter for identification purposes. + * @type string */ public $name; /** * Pre-processor function, handles HTML before HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string */ - public function preFilter($html, $config, $context) { + public function preFilter($html, $config, $context) + { return $html; } /** * Post-processor function, handles HTML after HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string */ - public function postFilter($html, $config, $context) { + public function postFilter($html, $config, $context) + { return $html; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php new file mode 100644 index 0000000000..08e62c16bf --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php @@ -0,0 +1,338 @@ +<?php + +// why is this a top level function? Because PHP 5.2.0 doesn't seem to +// understand how to interpret this filter if it's a static method. +// It's all really silly, but if we go this route it might be reasonable +// to coalesce all of these methods into one. +function htmlpurifier_filter_extractstyleblocks_muteerrorhandler() +{ +} + +/** + * This filter extracts <style> blocks from input HTML, cleans them up + * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks') + * so they can be used elsewhere in the document. + * + * @note + * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for + * sample usage. + * + * @note + * This filter can also be used on stylesheets not included in the + * document--something purists would probably prefer. Just directly + * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS() + */ +class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter +{ + /** + * @type string + */ + public $name = 'ExtractStyleBlocks'; + + /** + * @type array + */ + private $_styleMatches = array(); + + /** + * @type csstidy + */ + private $_tidy; + + /** + * @type HTMLPurifier_AttrDef_HTML_ID + */ + private $_id_attrdef; + + /** + * @type HTMLPurifier_AttrDef_CSS_Ident + */ + private $_class_attrdef; + + /** + * @type HTMLPurifier_AttrDef_Enum + */ + private $_enum_attrdef; + + public function __construct() + { + $this->_tidy = new csstidy(); + $this->_tidy->set_cfg('lowercase_s', false); + $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true); + $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident(); + $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum( + array( + 'first-child', + 'link', + 'visited', + 'active', + 'hover', + 'focus' + ) + ); + } + + /** + * Save the contents of CSS blocks to style matches + * @param array $matches preg_replace style $matches array + */ + protected function styleCallback($matches) + { + $this->_styleMatches[] = $matches[1]; + } + + /** + * Removes inline <style> tags from HTML, saves them for later use + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + * @todo Extend to indicate non-text/css style blocks + */ + public function preFilter($html, $config, $context) + { + $tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl'); + if ($tidy !== null) { + $this->_tidy = $tidy; + } + $html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html); + $style_blocks = $this->_styleMatches; + $this->_styleMatches = array(); // reset + $context->register('StyleBlocks', $style_blocks); // $context must not be reused + if ($this->_tidy) { + foreach ($style_blocks as &$style) { + $style = $this->cleanCSS($style, $config, $context); + } + } + return $html; + } + + /** + * Takes CSS (the stuff found in <style>) and cleans it. + * @warning Requires CSSTidy <http://csstidy.sourceforge.net/> + * @param string $css CSS styling to clean + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @throws HTMLPurifier_Exception + * @return string Cleaned CSS + */ + public function cleanCSS($css, $config, $context) + { + // prepare scope + $scope = $config->get('Filter.ExtractStyleBlocks.Scope'); + if ($scope !== null) { + $scopes = array_map('trim', explode(',', $scope)); + } else { + $scopes = array(); + } + // remove comments from CSS + $css = trim($css); + if (strncmp('<!--', $css, 4) === 0) { + $css = substr($css, 4); + } + if (strlen($css) > 3 && substr($css, -3) == '-->') { + $css = substr($css, 0, -3); + } + $css = trim($css); + set_error_handler('htmlpurifier_filter_extractstyleblocks_muteerrorhandler'); + $this->_tidy->parse($css); + restore_error_handler(); + $css_definition = $config->getDefinition('CSS'); + $html_definition = $config->getDefinition('HTML'); + $new_css = array(); + foreach ($this->_tidy->css as $k => $decls) { + // $decls are all CSS declarations inside an @ selector + $new_decls = array(); + foreach ($decls as $selector => $style) { + $selector = trim($selector); + if ($selector === '') { + continue; + } // should not happen + // Parse the selector + // Here is the relevant part of the CSS grammar: + // + // ruleset + // : selector [ ',' S* selector ]* '{' ... + // selector + // : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]? + // combinator + // : '+' S* + // : '>' S* + // simple_selector + // : element_name [ HASH | class | attrib | pseudo ]* + // | [ HASH | class | attrib | pseudo ]+ + // element_name + // : IDENT | '*' + // ; + // class + // : '.' IDENT + // ; + // attrib + // : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* + // [ IDENT | STRING ] S* ]? ']' + // ; + // pseudo + // : ':' [ IDENT | FUNCTION S* [IDENT S*]? ')' ] + // ; + // + // For reference, here are the relevant tokens: + // + // HASH #{name} + // IDENT {ident} + // INCLUDES == + // DASHMATCH |= + // STRING {string} + // FUNCTION {ident}\( + // + // And the lexical scanner tokens + // + // name {nmchar}+ + // nmchar [_a-z0-9-]|{nonascii}|{escape} + // nonascii [\240-\377] + // escape {unicode}|\\[^\r\n\f0-9a-f] + // unicode \\{h}}{1,6}(\r\n|[ \t\r\n\f])? + // ident -?{nmstart}{nmchar*} + // nmstart [_a-z]|{nonascii}|{escape} + // string {string1}|{string2} + // string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\" + // string2 \'([^\n\r\f\\"]|\\{nl}|{escape})*\' + // + // We'll implement a subset (in order to reduce attack + // surface); in particular: + // + // - No Unicode support + // - No escapes support + // - No string support (by proxy no attrib support) + // - element_name is matched against allowed + // elements (some people might find this + // annoying...) + // - Pseudo-elements one of :first-child, :link, + // :visited, :active, :hover, :focus + + // handle ruleset + $selectors = array_map('trim', explode(',', $selector)); + $new_selectors = array(); + foreach ($selectors as $sel) { + // split on +, > and spaces + $basic_selectors = preg_split('/\s*([+> ])\s*/', $sel, -1, PREG_SPLIT_DELIM_CAPTURE); + // even indices are chunks, odd indices are + // delimiters + $nsel = null; + $delim = null; // guaranteed to be non-null after + // two loop iterations + for ($i = 0, $c = count($basic_selectors); $i < $c; $i++) { + $x = $basic_selectors[$i]; + if ($i % 2) { + // delimiter + if ($x === ' ') { + $delim = ' '; + } else { + $delim = ' ' . $x . ' '; + } + } else { + // simple selector + $components = preg_split('/([#.:])/', $x, -1, PREG_SPLIT_DELIM_CAPTURE); + $sdelim = null; + $nx = null; + for ($j = 0, $cc = count($components); $j < $cc; $j++) { + $y = $components[$j]; + if ($j === 0) { + if ($y === '*' || isset($html_definition->info[$y = strtolower($y)])) { + $nx = $y; + } else { + // $nx stays null; this matters + // if we don't manage to find + // any valid selector content, + // in which case we ignore the + // outer $delim + } + } elseif ($j % 2) { + // set delimiter + $sdelim = $y; + } else { + $attrdef = null; + if ($sdelim === '#') { + $attrdef = $this->_id_attrdef; + } elseif ($sdelim === '.') { + $attrdef = $this->_class_attrdef; + } elseif ($sdelim === ':') { + $attrdef = $this->_enum_attrdef; + } else { + throw new HTMLPurifier_Exception('broken invariant sdelim and preg_split'); + } + $r = $attrdef->validate($y, $config, $context); + if ($r !== false) { + if ($r !== true) { + $y = $r; + } + if ($nx === null) { + $nx = ''; + } + $nx .= $sdelim . $y; + } + } + } + if ($nx !== null) { + if ($nsel === null) { + $nsel = $nx; + } else { + $nsel .= $delim . $nx; + } + } else { + // delimiters to the left of invalid + // basic selector ignored + } + } + } + if ($nsel !== null) { + if (!empty($scopes)) { + foreach ($scopes as $s) { + $new_selectors[] = "$s $nsel"; + } + } else { + $new_selectors[] = $nsel; + } + } + } + if (empty($new_selectors)) { + continue; + } + $selector = implode(', ', $new_selectors); + foreach ($style as $name => $value) { + if (!isset($css_definition->info[$name])) { + unset($style[$name]); + continue; + } + $def = $css_definition->info[$name]; + $ret = $def->validate($value, $config, $context); + if ($ret === false) { + unset($style[$name]); + } else { + $style[$name] = $ret; + } + } + $new_decls[$selector] = $style; + } + $new_css[$k] = $new_decls; + } + // remove stuff that shouldn't be used, could be reenabled + // after security risks are analyzed + $this->_tidy->css = $new_css; + $this->_tidy->import = array(); + $this->_tidy->charset = null; + $this->_tidy->namespace = null; + $css = $this->_tidy->print->plain(); + // we are going to escape any special characters <>& to ensure + // that no funny business occurs (i.e. </style> in a font-family prop). + if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { + $css = str_replace( + array('<', '>', '&'), + array('\3C ', '\3E ', '\26 '), + $css + ); + } + return $css; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php new file mode 100644 index 0000000000..276d8362fa --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php @@ -0,0 +1,65 @@ +<?php + +class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter +{ + + /** + * @type string + */ + public $name = 'YouTube'; + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function preFilter($html, $config, $context) + { + $pre_regex = '#<object[^>]+>.+?' . + '(?:http:)?//www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?</object>#s'; + $pre_replace = '<span class="youtube-embed">\1</span>'; + return preg_replace($pre_regex, $pre_replace, $html); + } + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function postFilter($html, $config, $context) + { + $post_regex = '#<span class="youtube-embed">((?:v|cp)/[A-Za-z0-9\-_=]+)</span>#'; + return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); + } + + /** + * @param $url + * @return string + */ + protected function armorUrl($url) + { + return str_replace('--', '--', $url); + } + + /** + * @param array $matches + * @return string + */ + protected function postFilterCallback($matches) + { + $url = $this->armorUrl($matches[1]); + return '<object width="425" height="350" type="application/x-shockwave-flash" ' . + 'data="//www.youtube.com/' . $url . '">' . + '<param name="movie" value="//www.youtube.com/' . $url . '"></param>' . + '<!--[if IE]>' . + '<embed src="//www.youtube.com/' . $url . '"' . + 'type="application/x-shockwave-flash"' . + 'wmode="transparent" width="425" height="350" />' . + '<![endif]-->' . + '</object>'; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Generator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php similarity index 56% rename from library/HTMLPurifier/Generator.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php index 4a62417271..6fb5687146 100644 --- a/library/HTMLPurifier/Generator.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php @@ -11,49 +11,64 @@ class HTMLPurifier_Generator { /** - * Whether or not generator should produce XML output + * Whether or not generator should produce XML output. + * @type bool */ private $_xhtml = true; /** - * :HACK: Whether or not generator should comment the insides of <script> tags + * :HACK: Whether or not generator should comment the insides of <script> tags. + * @type bool */ private $_scriptFix = false; /** * Cache of HTMLDefinition during HTML output to determine whether or * not attributes should be minimized. + * @type HTMLPurifier_HTMLDefinition */ private $_def; /** - * Cache of %Output.SortAttr + * Cache of %Output.SortAttr. + * @type bool */ private $_sortAttr; /** - * Cache of %Output.FlashCompat + * Cache of %Output.FlashCompat. + * @type bool */ private $_flashCompat; + /** + * Cache of %Output.FixInnerHTML. + * @type bool + */ + private $_innerHTMLFix; + /** * Stack for keeping track of object information when outputting IE * compatibility code. + * @type array */ private $_flashStack = array(); /** * Configuration for the generator + * @type HTMLPurifier_Config */ protected $config; /** - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context */ - public function __construct($config, $context) { + public function __construct($config, $context) + { $this->config = $config; $this->_scriptFix = $config->get('Output.CommentScriptContents'); + $this->_innerHTMLFix = $config->get('Output.FixInnerHTML'); $this->_sortAttr = $config->get('Output.SortAttr'); $this->_flashCompat = $config->get('Output.FlashCompat'); $this->_def = $config->getHTMLDefinition(); @@ -62,12 +77,14 @@ class HTMLPurifier_Generator /** * Generates HTML from an array of tokens. - * @param $tokens Array of HTMLPurifier_Token - * @param $config HTMLPurifier_Config object - * @return Generated HTML + * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token + * @return string Generated HTML */ - public function generateFromTokens($tokens) { - if (!$tokens) return ''; + public function generateFromTokens($tokens) + { + if (!$tokens) { + return ''; + } // Basic algorithm $html = ''; @@ -86,30 +103,41 @@ class HTMLPurifier_Generator // Tidy cleanup if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) { $tidy = new Tidy; - $tidy->parseString($html, array( - 'indent'=> true, - 'output-xhtml' => $this->_xhtml, - 'show-body-only' => true, - 'indent-spaces' => 2, - 'wrap' => 68, - ), 'utf8'); + $tidy->parseString( + $html, + array( + 'indent'=> true, + 'output-xhtml' => $this->_xhtml, + 'show-body-only' => true, + 'indent-spaces' => 2, + 'wrap' => 68, + ), + 'utf8' + ); $tidy->cleanRepair(); $html = (string) $tidy; // explicit cast necessary } // Normalize newlines to system defined value - $nl = $this->config->get('Output.Newline'); - if ($nl === null) $nl = PHP_EOL; - if ($nl !== "\n") $html = str_replace("\n", $nl, $html); + if ($this->config->get('Core.NormalizeNewlines')) { + $nl = $this->config->get('Output.Newline'); + if ($nl === null) { + $nl = PHP_EOL; + } + if ($nl !== "\n") { + $html = str_replace("\n", $nl, $html); + } + } return $html; } /** * Generates HTML from a single token. - * @param $token HTMLPurifier_Token object. - * @return Generated HTML + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string Generated HTML */ - public function generateFromToken($token) { + public function generateFromToken($token) + { if (!$token instanceof HTMLPurifier_Token) { trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING); return ''; @@ -130,19 +158,7 @@ class HTMLPurifier_Generator $_extra = ''; if ($this->_flashCompat) { if ($token->name == "object" && !empty($this->_flashStack)) { - $flash = array_pop($this->_flashStack); - $compat_token = new HTMLPurifier_Token_Empty("embed"); - foreach ($flash->attr as $name => $val) { - if ($name == "classid") continue; - if ($name == "type") continue; - if ($name == "data") $name = "src"; - $compat_token->attr[$name] = $val; - } - foreach ($flash->param as $name => $val) { - if ($name == "movie") $name = "src"; - $compat_token->attr[$name] = $val; - } - $_extra = "<!--[if IE]>".$this->generateFromToken($compat_token)."<![endif]-->"; + // doesn't do anything for now } } return $_extra . '</' . $token->name . '>'; @@ -169,11 +185,16 @@ class HTMLPurifier_Generator /** * Special case processor for the contents of script tags + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string * @warning This runs into problems if there's already a literal * --> somewhere inside the script contents. */ - public function generateScriptFromToken($token) { - if (!$token instanceof HTMLPurifier_Token_Text) return $this->generateFromToken($token); + public function generateScriptFromToken($token) + { + if (!$token instanceof HTMLPurifier_Token_Text) { + return $this->generateFromToken($token); + } // Thanks <http://lachy.id.au/log/2005/05/script-comments> $data = preg_replace('#//\s*$#', '', $token->data); return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>'; @@ -182,24 +203,60 @@ class HTMLPurifier_Generator /** * Generates attribute declarations from attribute array. * @note This does not include the leading or trailing space. - * @param $assoc_array_of_attributes Attribute array - * @param $element Name of element attributes are for, used to check + * @param array $assoc_array_of_attributes Attribute array + * @param string $element Name of element attributes are for, used to check * attribute minimization. - * @return Generate HTML fragment for insertion. + * @return string Generated HTML fragment for insertion. */ - public function generateAttributes($assoc_array_of_attributes, $element = false) { + public function generateAttributes($assoc_array_of_attributes, $element = '') + { $html = ''; - if ($this->_sortAttr) ksort($assoc_array_of_attributes); + if ($this->_sortAttr) { + ksort($assoc_array_of_attributes); + } foreach ($assoc_array_of_attributes as $key => $value) { if (!$this->_xhtml) { // Remove namespaced attributes - if (strpos($key, ':') !== false) continue; + if (strpos($key, ':') !== false) { + continue; + } // Check if we should minimize the attribute: val="val" -> val if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) { $html .= $key . ' '; continue; } } + // Workaround for Internet Explorer innerHTML bug. + // Essentially, Internet Explorer, when calculating + // innerHTML, omits quotes if there are no instances of + // angled brackets, quotes or spaces. However, when parsing + // HTML (for example, when you assign to innerHTML), it + // treats backticks as quotes. Thus, + // <img alt="``" /> + // becomes + // <img alt=`` /> + // becomes + // <img alt='' /> + // Fortunately, all we need to do is trigger an appropriate + // quoting style, which we do by adding an extra space. + // This also is consistent with the W3C spec, which states + // that user agents may ignore leading or trailing + // whitespace (in fact, most don't, at least for attributes + // like alt, but an extra space at the end is barely + // noticeable). Still, we have a configuration knob for + // this, since this transformation is not necesary if you + // don't process user input with innerHTML or you don't plan + // on supporting Internet Explorer. + if ($this->_innerHTMLFix) { + if (strpos($value, '`') !== false) { + // check if correct quoting style would not already be + // triggered + if (strcspn($value, '"\' <>') === strlen($value)) { + // protect! + $value .= ' '; + } + } + } $html .= $key.'="'.$this->escape($value).'" '; } return rtrim($html); @@ -210,15 +267,20 @@ class HTMLPurifier_Generator * @todo This really ought to be protected, but until we have a facility * for properly generating HTML here w/o using tokens, it stays * public. - * @param $string String data to escape for HTML. - * @param $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is + * @param string $string String data to escape for HTML. + * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is * permissible for non-attribute output. - * @return String escaped data. + * @return string escaped data. */ - public function escape($string, $quote = ENT_COMPAT) { + public function escape($string, $quote = null) + { + // Workaround for APC bug on Mac Leopard reported by sidepodcast + // http://htmlpurifier.org/phorum/read.php?3,4823,4846 + if ($quote === null) { + $quote = ENT_COMPAT; + } return htmlspecialchars($string, $quote, 'UTF-8'); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php similarity index 70% rename from library/HTMLPurifier/HTMLDefinition.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php index c99ac11eb2..9b7b334dd9 100644 --- a/library/HTMLPurifier/HTMLDefinition.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php @@ -29,60 +29,71 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition // FULLY-PUBLIC VARIABLES --------------------------------------------- /** - * Associative array of element names to HTMLPurifier_ElementDef + * Associative array of element names to HTMLPurifier_ElementDef. + * @type HTMLPurifier_ElementDef[] */ public $info = array(); /** * Associative array of global attribute name to attribute definition. + * @type array */ public $info_global_attr = array(); /** * String name of parent element HTML will be going into. + * @type string */ public $info_parent = 'div'; /** * Definition for parent element, allows parent element to be a * tag that's not allowed inside the HTML fragment. + * @type HTMLPurifier_ElementDef */ public $info_parent_def; /** - * String name of element used to wrap inline elements in block context + * String name of element used to wrap inline elements in block context. + * @type string * @note This is rarely used except for BLOCKQUOTEs in strict mode */ public $info_block_wrapper = 'p'; /** - * Associative array of deprecated tag name to HTMLPurifier_TagTransform + * Associative array of deprecated tag name to HTMLPurifier_TagTransform. + * @type array */ public $info_tag_transform = array(); /** * Indexed list of HTMLPurifier_AttrTransform to be performed before validation. + * @type HTMLPurifier_AttrTransform[] */ public $info_attr_transform_pre = array(); /** * Indexed list of HTMLPurifier_AttrTransform to be performed after validation. + * @type HTMLPurifier_AttrTransform[] */ public $info_attr_transform_post = array(); /** * Nested lookup array of content set name (Block, Inline) to * element name to whether or not it belongs in that content set. + * @type array */ public $info_content_sets = array(); /** * Indexed list of HTMLPurifier_Injector to be used. + * @type HTMLPurifier_Injector[] */ public $info_injector = array(); /** * Doctype object + * @type HTMLPurifier_Doctype */ public $doctype; @@ -94,12 +105,13 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition * Adds a custom attribute to a pre-existing element * @note This is strictly convenience, and does not have a corresponding * method in HTMLPurifier_HTMLModule - * @param $element_name String element name to add attribute to - * @param $attr_name String name of attribute - * @param $def Attribute definition, can be string or object, see + * @param string $element_name Element name to add attribute to + * @param string $attr_name Name of attribute + * @param mixed $def Attribute definition, can be string or object, see * HTMLPurifier_AttrTypes for details */ - public function addAttribute($element_name, $attr_name, $def) { + public function addAttribute($element_name, $attr_name, $def) + { $module = $this->getAnonymousModule(); if (!isset($module->info[$element_name])) { $element = $module->addBlankElement($element_name); @@ -111,10 +123,11 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition /** * Adds a custom element to your HTML definition - * @note See HTMLPurifier_HTMLModule::addElement for detailed + * @see HTMLPurifier_HTMLModule::addElement() for detailed * parameter and return value descriptions. */ - public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) { + public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) + { $module = $this->getAnonymousModule(); // assume that if the user is calling this, the element // is safe. This may not be a good idea @@ -125,10 +138,13 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition /** * Adds a blank element to your HTML definition, for overriding * existing behavior - * @note See HTMLPurifier_HTMLModule::addBlankElement for detailed + * @param string $element_name + * @return HTMLPurifier_ElementDef + * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed * parameter and return value descriptions. */ - public function addBlankElement($element_name) { + public function addBlankElement($element_name) + { $module = $this->getAnonymousModule(); $element = $module->addBlankElement($element_name); return $element; @@ -138,8 +154,10 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition * Retrieves a reference to the anonymous module, so you can * bust out advanced features without having to make your own * module. + * @return HTMLPurifier_HTMLModule */ - public function getAnonymousModule() { + public function getAnonymousModule() + { if (!$this->_anonModule) { $this->_anonModule = new HTMLPurifier_HTMLModule(); $this->_anonModule->name = 'Anonymous'; @@ -147,22 +165,33 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition return $this->_anonModule; } - private $_anonModule; - + private $_anonModule = null; // PUBLIC BUT INTERNAL VARIABLES -------------------------------------- + /** + * @type string + */ public $type = 'HTML'; - public $manager; /**< Instance of HTMLPurifier_HTMLModuleManager */ + + /** + * @type HTMLPurifier_HTMLModuleManager + */ + public $manager; /** * Performs low-cost, preliminary initialization. */ - public function __construct() { + public function __construct() + { $this->manager = new HTMLPurifier_HTMLModuleManager(); } - protected function doSetup($config) { + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetup($config) + { $this->processModules($config); $this->setupConfigStuff($config); unset($this->manager); @@ -176,9 +205,10 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition /** * Extract out the information from the manager + * @param HTMLPurifier_Config $config */ - protected function processModules($config) { - + protected function processModules($config) + { if ($this->_anonModule) { // for user specific changes // this is late-loaded so we don't have to deal with PHP4 @@ -191,40 +221,53 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition $this->doctype = $this->manager->doctype; foreach ($this->manager->modules as $module) { - foreach($module->info_tag_transform as $k => $v) { - if ($v === false) unset($this->info_tag_transform[$k]); - else $this->info_tag_transform[$k] = $v; + foreach ($module->info_tag_transform as $k => $v) { + if ($v === false) { + unset($this->info_tag_transform[$k]); + } else { + $this->info_tag_transform[$k] = $v; + } } - foreach($module->info_attr_transform_pre as $k => $v) { - if ($v === false) unset($this->info_attr_transform_pre[$k]); - else $this->info_attr_transform_pre[$k] = $v; + foreach ($module->info_attr_transform_pre as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_pre[$k]); + } else { + $this->info_attr_transform_pre[$k] = $v; + } } - foreach($module->info_attr_transform_post as $k => $v) { - if ($v === false) unset($this->info_attr_transform_post[$k]); - else $this->info_attr_transform_post[$k] = $v; + foreach ($module->info_attr_transform_post as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_post[$k]); + } else { + $this->info_attr_transform_post[$k] = $v; + } } foreach ($module->info_injector as $k => $v) { - if ($v === false) unset($this->info_injector[$k]); - else $this->info_injector[$k] = $v; + if ($v === false) { + unset($this->info_injector[$k]); + } else { + $this->info_injector[$k] = $v; + } } } - $this->info = $this->manager->getElements(); $this->info_content_sets = $this->manager->contentSets->lookup; - } /** * Sets up stuff based on config. We need a better way of doing this. + * @param HTMLPurifier_Config $config */ - protected function setupConfigStuff($config) { - + protected function setupConfigStuff($config) + { $block_wrapper = $config->get('HTML.BlockWrapper'); if (isset($this->info_content_sets['Block'][$block_wrapper])) { $this->info_block_wrapper = $block_wrapper; } else { - trigger_error('Cannot use non-block element as block wrapper', - E_USER_ERROR); + trigger_error( + 'Cannot use non-block element as block wrapper', + E_USER_ERROR + ); } $parent = $config->get('HTML.Parent'); @@ -233,14 +276,15 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition $this->info_parent = $parent; $this->info_parent_def = $def; } else { - trigger_error('Cannot use unrecognized element as parent', - E_USER_ERROR); + trigger_error( + 'Cannot use unrecognized element as parent', + E_USER_ERROR + ); $this->info_parent_def = $this->manager->getElement($this->info_parent, true); } // support template text - $support = "(for information on implementing this, see the ". - "support forums) "; + $support = "(for information on implementing this, see the support forums) "; // setup allowed elements ----------------------------------------- @@ -256,7 +300,9 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition if (is_array($allowed_elements)) { foreach ($this->info as $name => $d) { - if(!isset($allowed_elements[$name])) unset($this->info[$name]); + if (!isset($allowed_elements[$name])) { + unset($this->info[$name]); + } unset($allowed_elements[$name]); } // emit errors @@ -270,7 +316,6 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition $allowed_attributes_mutable = $allowed_attributes; // by copy! if (is_array($allowed_attributes)) { - // This actually doesn't do anything, since we went away from // global attributes. It's possible that userland code uses // it, but HTMLModuleManager doesn't! @@ -285,7 +330,9 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition unset($allowed_attributes_mutable[$key]); } } - if ($delete) unset($this->info_global_attr[$attr]); + if ($delete) { + unset($this->info_global_attr[$attr]); + } } foreach ($this->info as $tag => $info) { @@ -300,7 +347,16 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition unset($allowed_attributes_mutable[$key]); } } - if ($delete) unset($this->info[$tag]->attr[$attr]); + if ($delete) { + if ($this->info[$tag]->attr[$attr]->required) { + trigger_error( + "Required attribute '$attr' in element '$tag' " . + "was not allowed, which means '$tag' will not be allowed either", + E_USER_WARNING + ); + } + unset($this->info[$tag]->attr[$attr]); + } } } // emit errors @@ -313,23 +369,29 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition $element = htmlspecialchars($bits[0]); $attribute = htmlspecialchars($bits[1]); if (!isset($this->info[$element])) { - trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support"); + trigger_error( + "Cannot allow attribute '$attribute' if element " . + "'$element' is not allowed/supported $support" + ); } else { - trigger_error("Attribute '$attribute' in element '$element' not supported $support", - E_USER_WARNING); + trigger_error( + "Attribute '$attribute' in element '$element' not supported $support", + E_USER_WARNING + ); } break; } // otherwise fall through case 1: $attribute = htmlspecialchars($bits[0]); - trigger_error("Global attribute '$attribute' is not ". + trigger_error( + "Global attribute '$attribute' is not ". "supported in any elements $support", - E_USER_WARNING); + E_USER_WARNING + ); break; } } - } // setup forbidden elements --------------------------------------- @@ -343,25 +405,34 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition continue; } foreach ($info->attr as $attr => $x) { - if ( - isset($forbidden_attributes["$tag@$attr"]) || + if (isset($forbidden_attributes["$tag@$attr"]) || isset($forbidden_attributes["*@$attr"]) || isset($forbidden_attributes[$attr]) ) { unset($this->info[$tag]->attr[$attr]); continue; - } // this segment might get removed eventually - elseif (isset($forbidden_attributes["$tag.$attr"])) { + } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually // $tag.$attr are not user supplied, so no worries! - trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag@attr instead", E_USER_WARNING); + trigger_error( + "Error with $tag.$attr: tag.attr syntax not supported for " . + "HTML.ForbiddenAttributes; use tag@attr instead", + E_USER_WARNING + ); } } } foreach ($forbidden_attributes as $key => $v) { - if (strlen($key) < 2) continue; - if ($key[0] != '*') continue; + if (strlen($key) < 2) { + continue; + } + if ($key[0] != '*') { + continue; + } if ($key[1] == '.') { - trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", E_USER_WARNING); + trigger_error( + "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", + E_USER_WARNING + ); } } @@ -380,12 +451,12 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition * separate lists for processing. Format is element[attr1|attr2],element2... * @warning Although it's largely drawn from TinyMCE's implementation, * it is different, and you'll probably have to modify your lists - * @param $list String list to parse - * @param array($allowed_elements, $allowed_attributes) + * @param array $list String list to parse + * @return array * @todo Give this its own class, probably static interface */ - public function parseTinyMCEAllowedList($list) { - + public function parseTinyMCEAllowedList($list) + { $list = str_replace(array(' ', "\t"), '', $list); $elements = array(); @@ -393,7 +464,9 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition $chunks = preg_split('/(,|[\n\r]+)/', $list); foreach ($chunks as $chunk) { - if (empty($chunk)) continue; + if (empty($chunk)) { + continue; + } // remove TinyMCE element control characters if (!strpos($chunk, '[')) { $element = $chunk; @@ -401,20 +474,20 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition } else { list($element, $attr) = explode('[', $chunk); } - if ($element !== '*') $elements[$element] = true; - if (!$attr) continue; + if ($element !== '*') { + $elements[$element] = true; + } + if (!$attr) { + continue; + } $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ] $attr = explode('|', $attr); foreach ($attr as $key) { $attributes["$element.$key"] = true; } } - return array($elements, $attributes); - } - - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php similarity index 71% rename from library/HTMLPurifier/HTMLModule.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php index 072cf68084..bb3a9230b1 100644 --- a/library/HTMLPurifier/HTMLModule.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php @@ -21,13 +21,15 @@ class HTMLPurifier_HTMLModule // -- Overloadable ---------------------------------------------------- /** - * Short unique string identifier of the module + * Short unique string identifier of the module. + * @type string */ public $name; /** - * Informally, a list of elements this module changes. Not used in - * any significant way. + * Informally, a list of elements this module changes. + * Not used in any significant way. + * @type array */ public $elements = array(); @@ -35,6 +37,7 @@ class HTMLPurifier_HTMLModule * Associative array of element names to element definitions. * Some definitions may be incomplete, to be merged in later * with the full definition. + * @type array */ public $info = array(); @@ -43,6 +46,7 @@ class HTMLPurifier_HTMLModule * This is commonly used to, say, add an A element to the Inline * content set. This corresponds to an internal variable $content_sets * and NOT info_content_sets member variable of HTMLDefinition. + * @type array */ public $content_sets = array(); @@ -53,21 +57,25 @@ class HTMLPurifier_HTMLModule * the style attribute to the Core. Corresponds to HTMLDefinition's * attr_collections->info, since the object's data is only info, * with extra behavior associated with it. + * @type array */ public $attr_collections = array(); /** - * Associative array of deprecated tag name to HTMLPurifier_TagTransform + * Associative array of deprecated tag name to HTMLPurifier_TagTransform. + * @type array */ public $info_tag_transform = array(); /** * List of HTMLPurifier_AttrTransform to be performed before validation. + * @type array */ public $info_attr_transform_pre = array(); /** * List of HTMLPurifier_AttrTransform to be performed after validation. + * @type array */ public $info_attr_transform_post = array(); @@ -76,6 +84,7 @@ class HTMLPurifier_HTMLModule * An injector will only be invoked if all of it's pre-requisites are met; * if an injector fails setup, there will be no error; it will simply be * silently disabled. + * @type array */ public $info_injector = array(); @@ -84,6 +93,7 @@ class HTMLPurifier_HTMLModule * For optimization reasons: may save a call to a function. Be sure * to set it if you do implement getChildDef(), otherwise it will have * no effect! + * @type bool */ public $defines_child_def = false; @@ -94,6 +104,7 @@ class HTMLPurifier_HTMLModule * which is based off of safe HTML, to explicitly say, "This is safe," even * though there are modules which are "unsafe") * + * @type bool * @note Previously, safety could be applied at an element level granularity. * We've removed this ability, so in order to add "unsafe" elements * or attributes, a dedicated module with this property set to false @@ -106,51 +117,62 @@ class HTMLPurifier_HTMLModule * content_model and content_model_type member variables of * the HTMLPurifier_ElementDef class. There is a similar function * in HTMLPurifier_HTMLDefinition. - * @param $def HTMLPurifier_ElementDef instance + * @param HTMLPurifier_ElementDef $def * @return HTMLPurifier_ChildDef subclass */ - public function getChildDef($def) {return false;} + public function getChildDef($def) + { + return false; + } // -- Convenience ----------------------------------------------------- /** * Convenience function that sets up a new element - * @param $element Name of element to add - * @param $type What content set should element be registered to? + * @param string $element Name of element to add + * @param string|bool $type What content set should element be registered to? * Set as false to skip this step. - * @param $contents Allowed children in form of: + * @param string $contents Allowed children in form of: * "$content_model_type: $content_model" - * @param $attr_includes What attribute collections to register to + * @param array $attr_includes What attribute collections to register to * element? - * @param $attr What unique attributes does the element define? - * @note See ElementDef for in-depth descriptions of these parameters. - * @return Created element definition object, so you + * @param array $attr What unique attributes does the element define? + * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters. + * @return HTMLPurifier_ElementDef Created element definition object, so you * can set advanced parameters */ - public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) { + public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) + { $this->elements[] = $element; // parse content_model list($content_model_type, $content_model) = $this->parseContents($contents); // merge in attribute inclusions $this->mergeInAttrIncludes($attr, $attr_includes); // add element to content sets - if ($type) $this->addElementToContentSet($element, $type); + if ($type) { + $this->addElementToContentSet($element, $type); + } // create element $this->info[$element] = HTMLPurifier_ElementDef::create( - $content_model, $content_model_type, $attr + $content_model, + $content_model_type, + $attr ); // literal object $contents means direct child manipulation - if (!is_string($contents)) $this->info[$element]->child = $contents; + if (!is_string($contents)) { + $this->info[$element]->child = $contents; + } return $this->info[$element]; } /** * Convenience function that creates a totally blank, non-standalone * element. - * @param $element Name of element to create - * @return Created element + * @param string $element Name of element to create + * @return HTMLPurifier_ElementDef Created element */ - public function addBlankElement($element) { + public function addBlankElement($element) + { if (!isset($this->info[$element])) { $this->elements[] = $element; $this->info[$element] = new HTMLPurifier_ElementDef(); @@ -163,27 +185,35 @@ class HTMLPurifier_HTMLModule /** * Convenience function that registers an element to a content set - * @param Element to register - * @param Name content set (warning: case sensitive, usually upper-case + * @param string $element Element to register + * @param string $type Name content set (warning: case sensitive, usually upper-case * first letter) */ - public function addElementToContentSet($element, $type) { - if (!isset($this->content_sets[$type])) $this->content_sets[$type] = ''; - else $this->content_sets[$type] .= ' | '; + public function addElementToContentSet($element, $type) + { + if (!isset($this->content_sets[$type])) { + $this->content_sets[$type] = ''; + } else { + $this->content_sets[$type] .= ' | '; + } $this->content_sets[$type] .= $element; } /** * Convenience function that transforms single-string contents * into separate content model and content model type - * @param $contents Allowed children in form of: + * @param string $contents Allowed children in form of: * "$content_model_type: $content_model" + * @return array * @note If contents is an object, an array of two nulls will be * returned, and the callee needs to take the original $contents * and use it directly. */ - public function parseContents($contents) { - if (!is_string($contents)) return array(null, null); // defer + public function parseContents($contents) + { + if (!is_string($contents)) { + return array(null, null); + } // defer switch ($contents) { // check for shorthand content model forms case 'Empty': @@ -202,13 +232,17 @@ class HTMLPurifier_HTMLModule /** * Convenience function that merges a list of attribute includes into * an attribute array. - * @param $attr Reference to attr array to modify - * @param $attr_includes Array of includes / string include to merge in + * @param array $attr Reference to attr array to modify + * @param array $attr_includes Array of includes / string include to merge in */ - public function mergeInAttrIncludes(&$attr, $attr_includes) { + public function mergeInAttrIncludes(&$attr, $attr_includes) + { if (!is_array($attr_includes)) { - if (empty($attr_includes)) $attr_includes = array(); - else $attr_includes = array($attr_includes); + if (empty($attr_includes)) { + $attr_includes = array(); + } else { + $attr_includes = array($attr_includes); + } } $attr[0] = $attr_includes; } @@ -216,16 +250,21 @@ class HTMLPurifier_HTMLModule /** * Convenience function that generates a lookup table with boolean * true as value. - * @param $list List of values to turn into a lookup + * @param string $list List of values to turn into a lookup * @note You can also pass an arbitrary number of arguments in * place of the regular argument - * @return Lookup array equivalent of list + * @return array array equivalent of list */ - public function makeLookup($list) { - if (is_string($list)) $list = func_get_args(); + public function makeLookup($list) + { + if (is_string($list)) { + $list = func_get_args(); + } $ret = array(); foreach ($list as $value) { - if (is_null($value)) continue; + if (is_null($value)) { + continue; + } $ret[$value] = true; } return $ret; @@ -235,10 +274,11 @@ class HTMLPurifier_HTMLModule * Lazy load construction of the module after determining whether * or not it's needed, and also when a finalized configuration object * is available. - * @param $config Instance of HTMLPurifier_Config + * @param HTMLPurifier_Config $config */ - public function setup($config) {} - + public function setup($config) + { + } } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Bdo.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php similarity index 66% rename from library/HTMLPurifier/HTMLModule/Bdo.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php index 3d66f1b4e1..1e67c790d0 100644 --- a/library/HTMLPurifier/HTMLModule/Bdo.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php @@ -7,25 +7,38 @@ class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'Bdo'; + + /** + * @type array + */ public $attr_collections = array( 'I18N' => array('dir' => false) ); - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $bdo = $this->addElement( - 'bdo', 'Inline', 'Inline', array('Core', 'Lang'), + 'bdo', + 'Inline', + 'Inline', + array('Core', 'Lang'), array( 'dir' => 'Enum#ltr,rtl', // required // The Abstract Module specification has the attribute // inclusions wrong for bdo: bdo allows Lang ) ); - $bdo->attr_transform_post['required-dir'] = new HTMLPurifier_AttrTransform_BdoDir(); + $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir(); $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl'; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php similarity index 89% rename from library/HTMLPurifier/HTMLModule/CommonAttributes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php index 7c15da84fc..a96ab1bef1 100644 --- a/library/HTMLPurifier/HTMLModule/CommonAttributes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php @@ -2,8 +2,14 @@ class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'CommonAttributes'; + /** + * @type array + */ public $attr_collections = array( 'Core' => array( 0 => array('Style'), @@ -20,7 +26,6 @@ class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule 0 => array('Core', 'I18N') ) ); - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Edit.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php similarity index 71% rename from library/HTMLPurifier/HTMLModule/Edit.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php index ff93690555..a9042a3577 100644 --- a/library/HTMLPurifier/HTMLModule/Edit.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php @@ -7,9 +7,16 @@ class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'Edit'; - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow'; $attr = array( 'cite' => 'URI', @@ -26,13 +33,23 @@ class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule // Inline context ! Block context (exclamation mark is // separator, see getChildDef for parsing) + /** + * @type bool + */ public $defines_child_def = true; - public function getChildDef($def) { - if ($def->content_model_type != 'chameleon') return false; + + /** + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef_Chameleon + */ + public function getChildDef($def) + { + if ($def->content_model_type != 'chameleon') { + return false; + } $value = explode('!', $def->content_model); return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]); } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php new file mode 100644 index 0000000000..6f7ddbc05b --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php @@ -0,0 +1,190 @@ +<?php + +/** + * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4. + */ +class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule +{ + /** + * @type string + */ + public $name = 'Forms'; + + /** + * @type bool + */ + public $safe = false; + + /** + * @type array + */ + public $content_sets = array( + 'Block' => 'Form', + 'Inline' => 'Formctrl', + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $form = $this->addElement( + 'form', + 'Form', + 'Required: Heading | List | Block | fieldset', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accept-charset' => 'Charsets', + 'action*' => 'URI', + 'method' => 'Enum#get,post', + // really ContentType, but these two are the only ones used today + 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data', + ) + ); + $form->excludes = array('form' => true); + + $input = $this->addElement( + 'input', + 'Formctrl', + 'Empty', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accesskey' => 'Character', + 'alt' => 'Text', + 'checked' => 'Bool#checked', + 'disabled' => 'Bool#disabled', + 'maxlength' => 'Number', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'size' => 'Number', + 'src' => 'URI#embedded', + 'tabindex' => 'Number', + 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image', + 'value' => 'CDATA', + ) + ); + $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input(); + + $this->addElement( + 'select', + 'Formctrl', + 'Required: optgroup | option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'multiple' => 'Bool#multiple', + 'name' => 'CDATA', + 'size' => 'Number', + 'tabindex' => 'Number', + ) + ); + + $this->addElement( + 'option', + false, + 'Optional: #PCDATA', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label' => 'Text', + 'selected' => 'Bool#selected', + 'value' => 'CDATA', + ) + ); + // It's illegal for there to be more than one selected, but not + // be multiple. Also, no selected means undefined behavior. This might + // be difficult to implement; perhaps an injector, or a context variable. + + $textarea = $this->addElement( + 'textarea', + 'Formctrl', + 'Optional: #PCDATA', + 'Common', + array( + 'accesskey' => 'Character', + 'cols*' => 'Number', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'rows*' => 'Number', + 'tabindex' => 'Number', + ) + ); + $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea(); + + $button = $this->addElement( + 'button', + 'Formctrl', + 'Optional: #PCDATA | Heading | List | Block | Inline', + 'Common', + array( + 'accesskey' => 'Character', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'tabindex' => 'Number', + 'type' => 'Enum#button,submit,reset', + 'value' => 'CDATA', + ) + ); + + // For exclusions, ideally we'd specify content sets, not literal elements + $button->excludes = $this->makeLookup( + 'form', + 'fieldset', // Form + 'input', + 'select', + 'textarea', + 'label', + 'button', // Formctrl + 'a', // as per HTML 4.01 spec, this is omitted by modularization + 'isindex', + 'iframe' // legacy items + ); + + // Extra exclusion: img usemap="" is not permitted within this element. + // We'll omit this for now, since we don't have any good way of + // indicating it yet. + + // This is HIGHLY user-unfriendly; we need a custom child-def for this + $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common'); + + $label = $this->addElement( + 'label', + 'Formctrl', + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + // 'for' => 'IDREF', // IDREF not implemented, cannot allow + ) + ); + $label->excludes = array('label' => true); + + $this->addElement( + 'legend', + false, + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + ) + ); + + $this->addElement( + 'optgroup', + false, + 'Required: option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label*' => 'Text', + ) + ); + // Don't forget an injector for <isindex>. This one's a little complex + // because it maps to multiple elements. + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Hypertext.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php similarity index 78% rename from library/HTMLPurifier/HTMLModule/Hypertext.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php index d7e9bdd27e..72d7a31e68 100644 --- a/library/HTMLPurifier/HTMLModule/Hypertext.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php @@ -6,11 +6,21 @@ class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'Hypertext'; - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $a = $this->addElement( - 'a', 'Inline', 'Inline', 'Common', + 'a', + 'Inline', + 'Inline', + 'Common', array( // 'accesskey' => 'Character', // 'charset' => 'Charset', @@ -25,7 +35,6 @@ class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule $a->formatting = true; $a->excludes = array('a' => true); } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php new file mode 100644 index 0000000000..f7e7c91c02 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php @@ -0,0 +1,51 @@ +<?php + +/** + * XHTML 1.1 Iframe Module provides inline frames. + * + * @note This module is not considered safe unless an Iframe + * whitelisting mechanism is specified. Currently, the only + * such mechanism is %URL.SafeIframeRegexp + */ +class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule +{ + + /** + * @type string + */ + public $name = 'Iframe'; + + /** + * @type bool + */ + public $safe = false; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + if ($config->get('HTML.SafeIframe')) { + $this->safe = true; + } + $this->addElement( + 'iframe', + 'Inline', + 'Flow', + 'Common', + array( + 'src' => 'URI#embedded', + 'width' => 'Length', + 'height' => 'Length', + 'name' => 'ID', + 'scrolling' => 'Enum#yes,no,auto', + 'frameborder' => 'Enum#0,1', + 'longdesc' => 'URI', + 'marginheight' => 'Pixels', + 'marginwidth' => 'Pixels', + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Image.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php similarity index 80% rename from library/HTMLPurifier/HTMLModule/Image.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php index 948d435bcd..0f5fdb3baf 100644 --- a/library/HTMLPurifier/HTMLModule/Image.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php @@ -8,18 +8,28 @@ class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'Image'; - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $max = $config->get('HTML.MaxImgLength'); $img = $this->addElement( - 'img', 'Inline', 'Empty', 'Common', + 'img', + 'Inline', + 'Empty', + 'Common', array( 'alt*' => 'Text', // According to the spec, it's Length, but percents can // be abused, so we allow only Pixels. 'height' => 'Pixels#' . $max, - 'width' => 'Pixels#' . $max, + 'width' => 'Pixels#' . $max, 'longdesc' => 'URI', 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded ) @@ -34,7 +44,6 @@ class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule $img->attr_transform_post[] = new HTMLPurifier_AttrTransform_ImgRequired(); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Legacy.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php similarity index 67% rename from library/HTMLPurifier/HTMLModule/Legacy.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php index df33927ba6..86b5299579 100644 --- a/library/HTMLPurifier/HTMLModule/Legacy.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php @@ -18,29 +18,58 @@ class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'Legacy'; - public function setup($config) { - - $this->addElement('basefont', 'Inline', 'Empty', false, array( - 'color' => 'Color', - 'face' => 'Text', // extremely broad, we should - 'size' => 'Text', // tighten it - 'id' => 'ID' - )); + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement( + 'basefont', + 'Inline', + 'Empty', + null, + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + 'id' => 'ID' + ) + ); $this->addElement('center', 'Block', 'Flow', 'Common'); - $this->addElement('dir', 'Block', 'Required: li', 'Common', array( - 'compact' => 'Bool#compact' - )); - $this->addElement('font', 'Inline', 'Inline', array('Core', 'I18N'), array( - 'color' => 'Color', - 'face' => 'Text', // extremely broad, we should - 'size' => 'Text', // tighten it - )); - $this->addElement('menu', 'Block', 'Required: li', 'Common', array( - 'compact' => 'Bool#compact' - )); + $this->addElement( + 'dir', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); + $this->addElement( + 'font', + 'Inline', + 'Inline', + array('Core', 'I18N'), + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + ) + ); + $this->addElement( + 'menu', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); $s = $this->addElement('s', 'Inline', 'Inline', 'Common'); $s->formatting = true; @@ -89,7 +118,7 @@ class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule $hr->attr['width'] = 'Length'; $img = $this->addBlankElement('img'); - $img->attr['align'] = 'Enum#top,middle,bottom,left,right'; + $img->attr['align'] = 'IAlign'; $img->attr['border'] = 'Pixels'; $img->attr['hspace'] = 'Pixels'; $img->attr['vspace'] = 'Pixels'; @@ -98,7 +127,7 @@ class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule $li = $this->addBlankElement('li'); $li->attr['value'] = new HTMLPurifier_AttrDef_Integer(); - $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle'; + $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle'; $ol = $this->addBlankElement('ol'); $ol->attr['compact'] = 'Bool#compact'; @@ -136,8 +165,22 @@ class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule $ul->attr['compact'] = 'Bool#compact'; $ul->attr['type'] = 'Enum#square,disc,circle'; - } + // "safe" modifications to "unsafe" elements + // WARNING: If you want to add support for an unsafe, legacy + // attribute, make a new TrustedLegacy module with the trusted + // bit set appropriately + $form = $this->addBlankElement('form'); + $form->content_model = 'Flow | #PCDATA'; + $form->content_model_type = 'optional'; + $form->attr['target'] = 'FrameTarget'; + + $input = $this->addBlankElement('input'); + $input->attr['align'] = 'IAlign'; + + $legend = $this->addBlankElement('legend'); + $legend->attr['align'] = 'LAlign'; + } } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/List.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php similarity index 57% rename from library/HTMLPurifier/HTMLModule/List.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php index 74d4522f4e..7a20ff701c 100644 --- a/library/HTMLPurifier/HTMLModule/List.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php @@ -5,7 +5,9 @@ */ class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'List'; // According to the abstract schema, the List content set is a fully formed @@ -17,13 +19,26 @@ class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule // we don't have support for such nested expressions without using // the incredibly inefficient and draconic Custom ChildDef. + /** + * @type array + */ public $content_sets = array('Flow' => 'List'); - public function setup($config) { - $ol = $this->addElement('ol', 'List', 'Required: li', 'Common'); - $ol->wrap = "li"; - $ul = $this->addElement('ul', 'List', 'Required: li', 'Common'); - $ul->wrap = "li"; + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + // XXX The wrap attribute is handled by MakeWellFormed. This is all + // quite unsatisfactory, because we generated this + // *specifically* for lists, and now a big chunk of the handling + // is done properly by the List ChildDef. So actually, we just + // want enough information to make autoclosing work properly, + // and then hand off the tricky stuff to the ChildDef. + $ol->wrap = 'li'; + $ul->wrap = 'li'; $this->addElement('dl', 'List', 'Required: dt | dd', 'Common'); $this->addElement('li', false, 'Flow', 'Common'); @@ -31,7 +46,6 @@ class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule $this->addElement('dd', false, 'Flow', 'Common'); $this->addElement('dt', false, 'Inline', 'Common'); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Name.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php similarity index 65% rename from library/HTMLPurifier/HTMLModule/Name.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php index 05694b4504..60c0545154 100644 --- a/library/HTMLPurifier/HTMLModule/Name.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php @@ -2,20 +2,25 @@ class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'Name'; - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map'); foreach ($elements as $name) { $element = $this->addBlankElement($name); $element->attr['name'] = 'CDATA'; if (!$config->get('HTML.Attr.Name.UseCDATA')) { - $element->attr_transform_post['NameSync'] = new HTMLPurifier_AttrTransform_NameSync(); + $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync(); } } } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php new file mode 100644 index 0000000000..dc9410a895 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php @@ -0,0 +1,25 @@ +<?php + +/** + * Module adds the nofollow attribute transformation to a tags. It + * is enabled by HTML.Nofollow + */ +class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule +{ + + /** + * @type string + */ + public $name = 'Nofollow'; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $a = $this->addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow(); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php similarity index 79% rename from library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php index 5f1b14abb8..da722253ac 100644 --- a/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php @@ -2,8 +2,14 @@ class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'NonXMLCommonAttributes'; + /** + * @type array + */ public $attr_collections = array( 'Lang' => array( 'lang' => 'LanguageCode', diff --git a/library/HTMLPurifier/HTMLModule/Object.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php similarity index 71% rename from library/HTMLPurifier/HTMLModule/Object.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php index 193c1011f8..2f9efc5c88 100644 --- a/library/HTMLPurifier/HTMLModule/Object.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php @@ -7,13 +7,26 @@ */ class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'Object'; + + /** + * @type bool + */ public $safe = false; - public function setup($config) { - - $this->addElement('object', 'Inline', 'Optional: #PCDATA | Flow | param', 'Common', + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement( + 'object', + 'Inline', + 'Optional: #PCDATA | Flow | param', + 'Common', array( 'archive' => 'URI', 'classid' => 'URI', @@ -30,18 +43,20 @@ class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule ) ); - $this->addElement('param', false, 'Empty', false, + $this->addElement( + 'param', + false, + 'Empty', + null, array( 'id' => 'ID', 'name*' => 'Text', 'type' => 'Text', 'value' => 'Text', 'valuetype' => 'Enum#data,ref,object' - ) + ) ); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Presentation.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php similarity index 52% rename from library/HTMLPurifier/HTMLModule/Presentation.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php index 8ff0b5ed78..6458ce9d88 100644 --- a/library/HTMLPurifier/HTMLModule/Presentation.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php @@ -13,24 +13,30 @@ class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'Presentation'; - public function setup($config) { - $this->addElement('hr', 'Block', 'Empty', 'Common'); - $this->addElement('sub', 'Inline', 'Inline', 'Common'); - $this->addElement('sup', 'Inline', 'Inline', 'Common'); - $b = $this->addElement('b', 'Inline', 'Inline', 'Common'); + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement('hr', 'Block', 'Empty', 'Common'); + $this->addElement('sub', 'Inline', 'Inline', 'Common'); + $this->addElement('sup', 'Inline', 'Inline', 'Common'); + $b = $this->addElement('b', 'Inline', 'Inline', 'Common'); $b->formatting = true; - $big = $this->addElement('big', 'Inline', 'Inline', 'Common'); + $big = $this->addElement('big', 'Inline', 'Inline', 'Common'); $big->formatting = true; - $i = $this->addElement('i', 'Inline', 'Inline', 'Common'); + $i = $this->addElement('i', 'Inline', 'Inline', 'Common'); $i->formatting = true; - $small = $this->addElement('small', 'Inline', 'Inline', 'Common'); + $small = $this->addElement('small', 'Inline', 'Inline', 'Common'); $small->formatting = true; - $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common'); + $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common'); $tt->formatting = true; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Proprietary.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php similarity index 74% rename from library/HTMLPurifier/HTMLModule/Proprietary.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php index dd36a3de0e..5ee3c8e67f 100644 --- a/library/HTMLPurifier/HTMLModule/Proprietary.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php @@ -6,12 +6,21 @@ */ class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'Proprietary'; - public function setup($config) { - - $this->addElement('marquee', 'Inline', 'Flow', 'Common', + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement( + 'marquee', + 'Inline', + 'Flow', + 'Common', array( 'direction' => 'Enum#left,right,up,down', 'behavior' => 'Enum#alternate', @@ -25,9 +34,7 @@ class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule 'vspace' => 'Pixels', ) ); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Ruby.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php similarity index 77% rename from library/HTMLPurifier/HTMLModule/Ruby.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php index b26a0a30a0..a0d48924da 100644 --- a/library/HTMLPurifier/HTMLModule/Ruby.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php @@ -7,12 +7,22 @@ class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'Ruby'; - public function setup($config) { - $this->addElement('ruby', 'Inline', + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement( + 'ruby', + 'Inline', 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))', - 'Common'); + 'Common' + ); $this->addElement('rbc', false, 'Required: rb', 'Common'); $this->addElement('rtc', false, 'Required: rt', 'Common'); $rb = $this->addElement('rb', false, 'Inline', 'Common'); @@ -21,7 +31,6 @@ class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule $rt->excludes = array('ruby' => true); $this->addElement('rp', false, 'Optional: #PCDATA', 'Common'); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/SafeEmbed.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php similarity index 74% rename from library/HTMLPurifier/HTMLModule/SafeEmbed.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php index ea256716bb..04e6689ea6 100644 --- a/library/HTMLPurifier/HTMLModule/SafeEmbed.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php @@ -5,14 +5,22 @@ */ class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'SafeEmbed'; - public function setup($config) { - + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $max = $config->get('HTML.MaxImgLength'); $embed = $this->addElement( - 'embed', 'Inline', 'Empty', 'Common', + 'embed', + 'Inline', + 'Empty', + 'Common', array( 'src*' => 'URI#embedded', 'type' => 'Enum#application/x-shockwave-flash', @@ -21,14 +29,12 @@ class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule 'allowscriptaccess' => 'Enum#never', 'allownetworking' => 'Enum#internal', 'flashvars' => 'Text', - 'wmode' => 'Enum#window', + 'wmode' => 'Enum#window,transparent,opaque', 'name' => 'ID', ) ); $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed(); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/SafeObject.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php similarity index 67% rename from library/HTMLPurifier/HTMLModule/SafeObject.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php index 64ab8c0703..1297f80a39 100644 --- a/library/HTMLPurifier/HTMLModule/SafeObject.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php @@ -8,11 +8,16 @@ */ class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'SafeObject'; - public function setup($config) { - + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { // These definitions are not intrinsically safe: the attribute transforms // are a vital part of ensuring safety. @@ -25,18 +30,24 @@ class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule array( // While technically not required by the spec, we're forcing // it to this value. - 'type' => 'Enum#application/x-shockwave-flash', - 'width' => 'Pixels#' . $max, + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, 'height' => 'Pixels#' . $max, - 'data' => 'URI#embedded', - 'classid' => 'Enum#clsid:d27cdb6e-ae6d-11cf-96b8-444553540000', - 'codebase' => new HTMLPurifier_AttrDef_Enum(array( - 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0')), + 'data' => 'URI#embedded', + 'codebase' => new HTMLPurifier_AttrDef_Enum( + array( + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0' + ) + ), ) ); $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject(); - $param = $this->addElement('param', false, 'Empty', false, + $param = $this->addElement( + 'param', + false, + 'Empty', + false, array( 'id' => 'ID', 'name*' => 'Text', @@ -45,9 +56,7 @@ class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule ); $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam(); $this->info_injector[] = 'SafeObject'; - } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php new file mode 100644 index 0000000000..0330cd97f8 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php @@ -0,0 +1,40 @@ +<?php + +/** + * A "safe" script module. No inline JS is allowed, and pointed to JS + * files must match whitelist. + */ +class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule +{ + /** + * @type string + */ + public $name = 'SafeScripting'; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + // These definitions are not intrinsically safe: the attribute transforms + // are a vital part of ensuring safety. + + $allowed = $config->get('HTML.SafeScripting'); + $script = $this->addElement( + 'script', + 'Inline', + 'Empty', + null, + array( + // While technically not required by the spec, we're forcing + // it to this value. + 'type' => 'Enum#text/javascript', + 'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed)) + ) + ); + $script->attr_transform_pre[] = + $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired(); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Scripting.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php similarity index 76% rename from library/HTMLPurifier/HTMLModule/Scripting.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php index cecdea6c30..8b28a7b7ea 100644 --- a/library/HTMLPurifier/HTMLModule/Scripting.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php @@ -15,12 +15,31 @@ INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!! */ class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'Scripting'; + + /** + * @type array + */ public $elements = array('script', 'noscript'); + + /** + * @type array + */ public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript'); + + /** + * @type bool + */ public $safe = false; - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { // TODO: create custom child-definition for noscript that // auto-wraps stray #PCDATA in a similar manner to // blockquote's custom definition (we would use it but @@ -33,20 +52,20 @@ class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule // In theory, this could be safe, but I don't see any reason to // allow it. $this->info['noscript'] = new HTMLPurifier_ElementDef(); - $this->info['noscript']->attr = array( 0 => array('Common') ); + $this->info['noscript']->attr = array(0 => array('Common')); $this->info['noscript']->content_model = 'Heading | List | Block'; $this->info['noscript']->content_model_type = 'required'; $this->info['script'] = new HTMLPurifier_ElementDef(); $this->info['script']->attr = array( 'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')), - 'src' => new HTMLPurifier_AttrDef_URI(true), - 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript')) + 'src' => new HTMLPurifier_AttrDef_URI(true), + 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript')) ); $this->info['script']->content_model = '#PCDATA'; $this->info['script']->content_model_type = 'optional'; - $this->info['script']->attr_transform_pre['type'] = - $this->info['script']->attr_transform_post['type'] = + $this->info['script']->attr_transform_pre[] = + $this->info['script']->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired(); } } diff --git a/library/HTMLPurifier/HTMLModule/StyleAttribute.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php similarity index 78% rename from library/HTMLPurifier/HTMLModule/StyleAttribute.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php index eb78464cc0..497b832ae2 100644 --- a/library/HTMLPurifier/HTMLModule/StyleAttribute.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php @@ -6,8 +6,14 @@ */ class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'StyleAttribute'; + + /** + * @type array + */ public $attr_collections = array( // The inclusion routine differs from the Abstract Modules but // is in line with the DTD and XML Schemas. @@ -15,10 +21,13 @@ class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule 'Core' => array(0 => array('Style')) ); - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS(); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Tables.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php similarity index 75% rename from library/HTMLPurifier/HTMLModule/Tables.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php index f314ced3f8..8a0b3b4616 100644 --- a/library/HTMLPurifier/HTMLModule/Tables.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php @@ -5,15 +5,23 @@ */ class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'Tables'; - public function setup($config) { - + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $this->addElement('caption', false, 'Inline', 'Common'); - $this->addElement('table', 'Block', - new HTMLPurifier_ChildDef_Table(), 'Common', + $this->addElement( + 'table', + 'Block', + new HTMLPurifier_ChildDef_Table(), + 'Common', array( 'border' => 'Pixels', 'cellpadding' => 'Length', @@ -34,9 +42,12 @@ class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule $cell_t = array_merge( array( - 'abbr' => 'Text', + 'abbr' => 'Text', 'colspan' => 'Number', 'rowspan' => 'Number', + // Apparently, as of HTML5 this attribute only applies + // to 'th' elements. + 'scope' => 'Enum#row,col,rowgroup,colgroup', ), $cell_align ); @@ -47,20 +58,18 @@ class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule $cell_col = array_merge( array( - 'span' => 'Number', + 'span' => 'Number', 'width' => 'MultiLength', ), $cell_align ); - $this->addElement('col', false, 'Empty', 'Common', $cell_col); + $this->addElement('col', false, 'Empty', 'Common', $cell_col); $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col); $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align); $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align); $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Target.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php similarity index 77% rename from library/HTMLPurifier/HTMLModule/Target.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php index 2b844ecc45..b188ac9369 100644 --- a/library/HTMLPurifier/HTMLModule/Target.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php @@ -5,10 +5,16 @@ */ class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'Target'; - public function setup($config) { + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { $elements = array('a'); foreach ($elements as $name) { $e = $this->addBlankElement($name); @@ -17,7 +23,6 @@ class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule ); } } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php new file mode 100644 index 0000000000..58ccc68941 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php @@ -0,0 +1,24 @@ +<?php + +/** + * Module adds the target=blank attribute transformation to a tags. It + * is enabled by HTML.TargetBlank + */ +class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule +{ + /** + * @type string + */ + public $name = 'TargetBlank'; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $a = $this->addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank(); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php similarity index 58% rename from library/HTMLPurifier/HTMLModule/Text.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php index ae77c71886..7a65e0048f 100644 --- a/library/HTMLPurifier/HTMLModule/Text.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php @@ -14,43 +14,59 @@ */ class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule { - + /** + * @type string + */ public $name = 'Text'; + + /** + * @type array + */ public $content_sets = array( 'Flow' => 'Heading | Block | Inline' ); - public function setup($config) { - + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { // Inline Phrasal ------------------------------------------------- - $this->addElement('abbr', 'Inline', 'Inline', 'Common'); + $this->addElement('abbr', 'Inline', 'Inline', 'Common'); $this->addElement('acronym', 'Inline', 'Inline', 'Common'); - $this->addElement('cite', 'Inline', 'Inline', 'Common'); - $this->addElement('dfn', 'Inline', 'Inline', 'Common'); - $this->addElement('kbd', 'Inline', 'Inline', 'Common'); - $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI')); - $this->addElement('samp', 'Inline', 'Inline', 'Common'); - $this->addElement('var', 'Inline', 'Inline', 'Common'); + $this->addElement('cite', 'Inline', 'Inline', 'Common'); + $this->addElement('dfn', 'Inline', 'Inline', 'Common'); + $this->addElement('kbd', 'Inline', 'Inline', 'Common'); + $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI')); + $this->addElement('samp', 'Inline', 'Inline', 'Common'); + $this->addElement('var', 'Inline', 'Inline', 'Common'); - $em = $this->addElement('em', 'Inline', 'Inline', 'Common'); + $em = $this->addElement('em', 'Inline', 'Inline', 'Common'); $em->formatting = true; - $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common'); + $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common'); $strong->formatting = true; - $code = $this->addElement('code', 'Inline', 'Inline', 'Common'); + $code = $this->addElement('code', 'Inline', 'Inline', 'Common'); $code->formatting = true; // Inline Structural ---------------------------------------------- $this->addElement('span', 'Inline', 'Inline', 'Common'); - $this->addElement('br', 'Inline', 'Empty', 'Core'); + $this->addElement('br', 'Inline', 'Empty', 'Core'); // Block Phrasal -------------------------------------------------- - $this->addElement('address', 'Block', 'Inline', 'Common'); - $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI') ); + $this->addElement('address', 'Block', 'Inline', 'Common'); + $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI')); $pre = $this->addElement('pre', 'Block', 'Inline', 'Common'); $pre->excludes = $this->makeLookup( - 'img', 'big', 'small', 'object', 'applet', 'font', 'basefont' ); + 'img', + 'big', + 'small', + 'object', + 'applet', + 'font', + 'basefont' + ); $this->addElement('h1', 'Heading', 'Inline', 'Common'); $this->addElement('h2', 'Heading', 'Inline', 'Common'); $this->addElement('h3', 'Heading', 'Inline', 'Common'); @@ -60,12 +76,12 @@ class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule // Block Structural ----------------------------------------------- $p = $this->addElement('p', 'Block', 'Inline', 'Common'); - $p->autoclose = array_flip(array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul")); + $p->autoclose = array_flip( + array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul") + ); $this->addElement('div', 'Block', 'Flow', 'Common'); - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Tidy.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php similarity index 78% rename from library/HTMLPurifier/HTMLModule/Tidy.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php index 21783f18eb..08aa232470 100644 --- a/library/HTMLPurifier/HTMLModule/Tidy.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php @@ -7,36 +7,41 @@ */ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule { - /** - * List of supported levels. Index zero is a special case "no fixes" - * level. + * List of supported levels. + * Index zero is a special case "no fixes" level. + * @type array */ public $levels = array(0 => 'none', 'light', 'medium', 'heavy'); /** - * Default level to place all fixes in. Disabled by default + * Default level to place all fixes in. + * Disabled by default. + * @type string */ public $defaultLevel = null; /** - * Lists of fixes used by getFixesForLevel(). Format is: + * Lists of fixes used by getFixesForLevel(). + * Format is: * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2'); + * @type array */ public $fixesForLevel = array( - 'light' => array(), + 'light' => array(), 'medium' => array(), - 'heavy' => array() + 'heavy' => array() ); /** * Lazy load constructs the module by determining the necessary * fixes to create and then delegating to the populate() function. + * @param HTMLPurifier_Config $config * @todo Wildcard matching and error reporting when an added or * subtracted fix has no effect. */ - public function setup($config) { - + public function setup($config) + { // create fixes, initialize fixesForLevel $fixes = $this->makeFixes(); $this->makeFixesForLevel($fixes); @@ -46,38 +51,38 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule $fixes_lookup = $this->getFixesForLevel($level); // get custom fix declarations: these need namespace processing - $add_fixes = $config->get('HTML.TidyAdd'); + $add_fixes = $config->get('HTML.TidyAdd'); $remove_fixes = $config->get('HTML.TidyRemove'); foreach ($fixes as $name => $fix) { // needs to be refactored a little to implement globbing - if ( - isset($remove_fixes[$name]) || - (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name])) - ) { + if (isset($remove_fixes[$name]) || + (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) { unset($fixes[$name]); } } // populate this module with necessary fixes $this->populate($fixes); - } /** * Retrieves all fixes per a level, returning fixes for that specific * level as well as all levels below it. - * @param $level String level identifier, see $levels for valid values - * @return Lookup up table of fixes + * @param string $level level identifier, see $levels for valid values + * @return array Lookup up table of fixes */ - public function getFixesForLevel($level) { + public function getFixesForLevel($level) + { if ($level == $this->levels[0]) { return array(); } $activated_levels = array(); for ($i = 1, $c = count($this->levels); $i < $c; $i++) { $activated_levels[] = $this->levels[$i]; - if ($this->levels[$i] == $level) break; + if ($this->levels[$i] == $level) { + break; + } } if ($i == $c) { trigger_error( @@ -99,9 +104,13 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule * Dynamically populates the $fixesForLevel member variable using * the fixes array. It may be custom overloaded, used in conjunction * with $defaultLevel, or not used at all. + * @param array $fixes */ - public function makeFixesForLevel($fixes) { - if (!isset($this->defaultLevel)) return; + public function makeFixesForLevel($fixes) + { + if (!isset($this->defaultLevel)) { + return; + } if (!isset($this->fixesForLevel[$this->defaultLevel])) { trigger_error( 'Default level ' . $this->defaultLevel . ' does not exist', @@ -115,9 +124,10 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule /** * Populates the module with transforms and other special-case code * based on a list of fixes passed to it - * @param $lookup Lookup table of fixes to activate + * @param array $fixes Lookup table of fixes to activate */ - public function populate($fixes) { + public function populate($fixes) + { foreach ($fixes as $name => $fix) { // determine what the fix is for list($type, $params) = $this->getFixType($name); @@ -169,20 +179,31 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule * @note $fix_parameters is type dependant, see populate() for usage * of these parameters */ - public function getFixType($name) { + public function getFixType($name) + { // parse it $property = $attr = null; - if (strpos($name, '#') !== false) list($name, $property) = explode('#', $name); - if (strpos($name, '@') !== false) list($name, $attr) = explode('@', $name); + if (strpos($name, '#') !== false) { + list($name, $property) = explode('#', $name); + } + if (strpos($name, '@') !== false) { + list($name, $attr) = explode('@', $name); + } // figure out the parameters $params = array(); - if ($name !== '') $params['element'] = $name; - if (!is_null($attr)) $params['attr'] = $attr; + if ($name !== '') { + $params['element'] = $name; + } + if (!is_null($attr)) { + $params['attr'] = $attr; + } // special case: attribute transform if (!is_null($attr)) { - if (is_null($property)) $property = 'pre'; + if (is_null($property)) { + $property = 'pre'; + } $type = 'attr_transform_' . $property; return array($type, $params); } @@ -199,9 +220,11 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule /** * Defines all fixes the module will perform in a compact * associative array of fix name to fix implementation. + * @return array */ - public function makeFixes() {} - + public function makeFixes() + { + } } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Tidy/Name.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php similarity index 80% rename from library/HTMLPurifier/HTMLModule/Tidy/Name.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php index 61ff85ce2f..a995161b28 100644 --- a/library/HTMLPurifier/HTMLModule/Tidy/Name.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php @@ -5,18 +5,27 @@ */ class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy { + /** + * @type string + */ public $name = 'Tidy_Name'; + + /** + * @type string + */ public $defaultLevel = 'heavy'; - public function makeFixes() { + /** + * @return array + */ + public function makeFixes() + { $r = array(); - // @name for img, a ----------------------------------------------- // Technically, it's allowed even on strict, so we allow authors to use // it. However, it's deprecated in future versions of XHTML. $r['img@name'] = $r['a@name'] = new HTMLPurifier_AttrTransform_Name(); - return $r; } } diff --git a/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php similarity index 85% rename from library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php index 14c15c4a06..3326438216 100644 --- a/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Proprietary.php @@ -3,10 +3,21 @@ class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy { + /** + * @type string + */ public $name = 'Tidy_Proprietary'; + + /** + * @type string + */ public $defaultLevel = 'light'; - public function makeFixes() { + /** + * @return array + */ + public function makeFixes() + { $r = array(); $r['table@background'] = new HTMLPurifier_AttrTransform_Background(); $r['td@background'] = new HTMLPurifier_AttrTransform_Background(); @@ -18,7 +29,6 @@ class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_T $r['table@height'] = new HTMLPurifier_AttrTransform_Length('height'); return $r; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php new file mode 100644 index 0000000000..803c44fabd --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Strict.php @@ -0,0 +1,43 @@ +<?php + +class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 +{ + /** + * @type string + */ + public $name = 'Tidy_Strict'; + + /** + * @type string + */ + public $defaultLevel = 'light'; + + /** + * @return array + */ + public function makeFixes() + { + $r = parent::makeFixes(); + $r['blockquote#content_model_type'] = 'strictblockquote'; + return $r; + } + + /** + * @type bool + */ + public $defines_child_def = true; + + /** + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef_StrictBlockquote + */ + public function getChildDef($def) + { + if ($def->content_model_type != 'strictblockquote') { + return parent::getChildDef($def); + } + return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php similarity index 74% rename from library/HTMLPurifier/HTMLModule/Tidy/Transitional.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php index 9960b1dd10..c095ad9745 100644 --- a/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php @@ -2,7 +2,14 @@ class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 { + /** + * @type string + */ public $name = 'Tidy_Transitional'; + + /** + * @type string + */ public $defaultLevel = 'heavy'; } diff --git a/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php similarity index 66% rename from library/HTMLPurifier/HTMLModule/Tidy/XHTML.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php index db5a378e53..3ecddc434b 100644 --- a/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTML.php @@ -2,16 +2,25 @@ class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy { - + /** + * @type string + */ public $name = 'Tidy_XHTML'; + + /** + * @type string + */ public $defaultLevel = 'medium'; - public function makeFixes() { + /** + * @return array + */ + public function makeFixes() + { $r = array(); $r['@lang'] = new HTMLPurifier_AttrTransform_Lang(); return $r; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php similarity index 50% rename from library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php index 02e9438139..c4f16a4dc9 100644 --- a/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php @@ -3,69 +3,86 @@ class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy { - public function makeFixes() { - + /** + * @return array + */ + public function makeFixes() + { $r = array(); // == deprecated tag transforms =================================== - $r['font'] = new HTMLPurifier_TagTransform_Font(); - $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul'); - $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul'); - $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;'); - $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;'); - $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;'); + $r['font'] = new HTMLPurifier_TagTransform_Font(); + $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul'); + $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul'); + $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;'); + $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;'); + $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;'); $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;'); // == deprecated attribute transforms ============================= $r['caption@align'] = - new HTMLPurifier_AttrTransform_EnumToCSS('align', array( - // we're following IE's behavior, not Firefox's, due - // to the fact that no one supports caption-side:right, - // W3C included (with CSS 2.1). This is a slightly - // unreasonable attribute! - 'left' => 'text-align:left;', - 'right' => 'text-align:right;', - 'top' => 'caption-side:top;', - 'bottom' => 'caption-side:bottom;' // not supported by IE - )); + new HTMLPurifier_AttrTransform_EnumToCSS( + 'align', + array( + // we're following IE's behavior, not Firefox's, due + // to the fact that no one supports caption-side:right, + // W3C included (with CSS 2.1). This is a slightly + // unreasonable attribute! + 'left' => 'text-align:left;', + 'right' => 'text-align:right;', + 'top' => 'caption-side:top;', + 'bottom' => 'caption-side:bottom;' // not supported by IE + ) + ); // @align for img ------------------------------------------------- $r['img@align'] = - new HTMLPurifier_AttrTransform_EnumToCSS('align', array( - 'left' => 'float:left;', - 'right' => 'float:right;', - 'top' => 'vertical-align:top;', - 'middle' => 'vertical-align:middle;', - 'bottom' => 'vertical-align:baseline;', - )); + new HTMLPurifier_AttrTransform_EnumToCSS( + 'align', + array( + 'left' => 'float:left;', + 'right' => 'float:right;', + 'top' => 'vertical-align:top;', + 'middle' => 'vertical-align:middle;', + 'bottom' => 'vertical-align:baseline;', + ) + ); // @align for table ----------------------------------------------- $r['table@align'] = - new HTMLPurifier_AttrTransform_EnumToCSS('align', array( - 'left' => 'float:left;', - 'center' => 'margin-left:auto;margin-right:auto;', - 'right' => 'float:right;' - )); + new HTMLPurifier_AttrTransform_EnumToCSS( + 'align', + array( + 'left' => 'float:left;', + 'center' => 'margin-left:auto;margin-right:auto;', + 'right' => 'float:right;' + ) + ); // @align for hr ----------------------------------------------- $r['hr@align'] = - new HTMLPurifier_AttrTransform_EnumToCSS('align', array( - // we use both text-align and margin because these work - // for different browsers (IE and Firefox, respectively) - // and the melange makes for a pretty cross-compatible - // solution - 'left' => 'margin-left:0;margin-right:auto;text-align:left;', - 'center' => 'margin-left:auto;margin-right:auto;text-align:center;', - 'right' => 'margin-left:auto;margin-right:0;text-align:right;' - )); + new HTMLPurifier_AttrTransform_EnumToCSS( + 'align', + array( + // we use both text-align and margin because these work + // for different browsers (IE and Firefox, respectively) + // and the melange makes for a pretty cross-compatible + // solution + 'left' => 'margin-left:0;margin-right:auto;text-align:left;', + 'center' => 'margin-left:auto;margin-right:auto;text-align:center;', + 'right' => 'margin-left:auto;margin-right:0;text-align:right;' + ) + ); // @align for h1, h2, h3, h4, h5, h6, p, div ---------------------- // {{{ - $align_lookup = array(); - $align_values = array('left', 'right', 'center', 'justify'); - foreach ($align_values as $v) $align_lookup[$v] = "text-align:$v;"; + $align_lookup = array(); + $align_values = array('left', 'right', 'center', 'justify'); + foreach ($align_values as $v) { + $align_lookup[$v] = "text-align:$v;"; + } // }}} $r['h1@align'] = $r['h2@align'] = @@ -73,7 +90,7 @@ class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule $r['h4@align'] = $r['h5@align'] = $r['h6@align'] = - $r['p@align'] = + $r['p@align'] = $r['div@align'] = new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup); @@ -88,12 +105,15 @@ class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule // @clear for br -------------------------------------------------- $r['br@clear'] = - new HTMLPurifier_AttrTransform_EnumToCSS('clear', array( - 'left' => 'clear:left;', - 'right' => 'clear:right;', - 'all' => 'clear:both;', - 'none' => 'clear:none;', - )); + new HTMLPurifier_AttrTransform_EnumToCSS( + 'clear', + array( + 'left' => 'clear:left;', + 'right' => 'clear:right;', + 'all' => 'clear:both;', + 'none' => 'clear:none;', + ) + ); // @height for td, th --------------------------------------------- $r['td@height'] = @@ -125,19 +145,19 @@ class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule // @type for li, ol, ul ------------------------------------------- // {{{ - $ul_types = array( - 'disc' => 'list-style-type:disc;', - 'square' => 'list-style-type:square;', - 'circle' => 'list-style-type:circle;' - ); - $ol_types = array( - '1' => 'list-style-type:decimal;', - 'i' => 'list-style-type:lower-roman;', - 'I' => 'list-style-type:upper-roman;', - 'a' => 'list-style-type:lower-alpha;', - 'A' => 'list-style-type:upper-alpha;' - ); - $li_types = $ul_types + $ol_types; + $ul_types = array( + 'disc' => 'list-style-type:disc;', + 'square' => 'list-style-type:square;', + 'circle' => 'list-style-type:circle;' + ); + $ol_types = array( + '1' => 'list-style-type:decimal;', + 'i' => 'list-style-type:lower-roman;', + 'I' => 'list-style-type:upper-roman;', + 'a' => 'list-style-type:lower-alpha;', + 'A' => 'list-style-type:upper-alpha;' + ); + $li_types = $ul_types + $ol_types; // }}} $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types); @@ -153,9 +173,7 @@ class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width'); return $r; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php similarity index 79% rename from library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php index 9c0e031984..01dbe9deb6 100644 --- a/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php @@ -2,8 +2,14 @@ class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule { + /** + * @type string + */ public $name = 'XMLCommonAttributes'; + /** + * @type array + */ public $attr_collections = array( 'Lang' => array( 'xml:lang' => 'LanguageCode', diff --git a/library/HTMLPurifier/HTMLModuleManager.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php similarity index 75% rename from library/HTMLPurifier/HTMLModuleManager.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php index f5c4a1d2cb..f3a17cb03b 100644 --- a/library/HTMLPurifier/HTMLModuleManager.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php @@ -4,57 +4,75 @@ class HTMLPurifier_HTMLModuleManager { /** - * Instance of HTMLPurifier_DoctypeRegistry + * @type HTMLPurifier_DoctypeRegistry */ public $doctypes; /** - * Instance of current doctype + * Instance of current doctype. + * @type string */ public $doctype; /** - * Instance of HTMLPurifier_AttrTypes + * @type HTMLPurifier_AttrTypes */ public $attrTypes; /** * Active instances of modules for the specified doctype are * indexed, by name, in this array. + * @type HTMLPurifier_HTMLModule[] */ public $modules = array(); /** - * Array of recognized HTMLPurifier_Module instances, indexed by - * module's class name. This array is usually lazy loaded, but a + * Array of recognized HTMLPurifier_HTMLModule instances, + * indexed by module's class name. This array is usually lazy loaded, but a * user can overload a module by pre-emptively registering it. + * @type HTMLPurifier_HTMLModule[] */ public $registeredModules = array(); /** - * List of extra modules that were added by the user using addModule(). - * These get unconditionally merged into the current doctype, whatever + * List of extra modules that were added by the user + * using addModule(). These get unconditionally merged into the current doctype, whatever * it may be. + * @type HTMLPurifier_HTMLModule[] */ public $userModules = array(); /** * Associative array of element name to list of modules that have * definitions for the element; this array is dynamically filled. + * @type array */ public $elementLookup = array(); - /** List of prefixes we should use for registering small names */ + /** + * List of prefixes we should use for registering small names. + * @type array + */ public $prefixes = array('HTMLPurifier_HTMLModule_'); - public $contentSets; /**< Instance of HTMLPurifier_ContentSets */ - public $attrCollections; /**< Instance of HTMLPurifier_AttrCollections */ + /** + * @type HTMLPurifier_ContentSets + */ + public $contentSets; - /** If set to true, unsafe elements and attributes will be allowed */ + /** + * @type HTMLPurifier_AttrCollections + */ + public $attrCollections; + + /** + * If set to true, unsafe elements and attributes will be allowed. + * @type bool + */ public $trusted = false; - public function __construct() { - + public function __construct() + { // editable internal objects $this->attrTypes = new HTMLPurifier_AttrTypes(); $this->doctypes = new HTMLPurifier_DoctypeRegistry(); @@ -65,17 +83,18 @@ class HTMLPurifier_HTMLModuleManager 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image', 'StyleAttribute', // Unsafe: - 'Scripting', 'Object', 'Forms', + 'Scripting', 'Object', 'Forms', // Sorta legacy, but present in strict: 'Name', ); - $transitional = array('Legacy', 'Target'); + $transitional = array('Legacy', 'Target', 'Iframe'); $xml = array('XMLCommonAttributes'); $non_xml = array('NonXMLCommonAttributes'); // setup basic doctypes $this->doctypes->register( - 'HTML 4.01 Transitional', false, + 'HTML 4.01 Transitional', + false, array_merge($common, $transitional, $non_xml), array('Tidy_Transitional', 'Tidy_Proprietary'), array(), @@ -84,7 +103,8 @@ class HTMLPurifier_HTMLModuleManager ); $this->doctypes->register( - 'HTML 4.01 Strict', false, + 'HTML 4.01 Strict', + false, array_merge($common, $non_xml), array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'), array(), @@ -93,7 +113,8 @@ class HTMLPurifier_HTMLModuleManager ); $this->doctypes->register( - 'XHTML 1.0 Transitional', true, + 'XHTML 1.0 Transitional', + true, array_merge($common, $transitional, $xml, $non_xml), array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'), array(), @@ -102,7 +123,8 @@ class HTMLPurifier_HTMLModuleManager ); $this->doctypes->register( - 'XHTML 1.0 Strict', true, + 'XHTML 1.0 Strict', + true, array_merge($common, $xml, $non_xml), array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'), array(), @@ -111,8 +133,11 @@ class HTMLPurifier_HTMLModuleManager ); $this->doctypes->register( - 'XHTML 1.1', true, - array_merge($common, $xml, array('Ruby')), + 'XHTML 1.1', + true, + // Iframe is a real XHTML 1.1 module, despite being + // "transitional"! + array_merge($common, $xml, array('Ruby', 'Iframe')), array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1 array(), '-//W3C//DTD XHTML 1.1//EN', @@ -142,7 +167,8 @@ class HTMLPurifier_HTMLModuleManager * your module manually. All modules must have been included * externally: registerModule will not perform inclusions for you! */ - public function registerModule($module, $overload = false) { + public function registerModule($module, $overload = false) + { if (is_string($module)) { // attempt to load the module $original_module = $module; @@ -157,8 +183,10 @@ class HTMLPurifier_HTMLModuleManager if (!$ok) { $module = $original_module; if (!class_exists($module)) { - trigger_error($original_module . ' module does not exist', - E_USER_ERROR); + trigger_error( + $original_module . ' module does not exist', + E_USER_ERROR + ); return; } } @@ -178,9 +206,12 @@ class HTMLPurifier_HTMLModuleManager * Adds a module to the current doctype by first registering it, * and then tacking it on to the active doctype */ - public function addModule($module) { + public function addModule($module) + { $this->registerModule($module); - if (is_object($module)) $module = $module->name; + if (is_object($module)) { + $module = $module->name; + } $this->userModules[] = $module; } @@ -188,17 +219,18 @@ class HTMLPurifier_HTMLModuleManager * Adds a class prefix that registerModule() will use to resolve a * string name to a concrete class */ - public function addPrefix($prefix) { + public function addPrefix($prefix) + { $this->prefixes[] = $prefix; } /** * Performs processing on modules, after being called you may * use getElement() and getElements() - * @param $config Instance of HTMLPurifier_Config + * @param HTMLPurifier_Config $config */ - public function setup($config) { - + public function setup($config) + { $this->trusted = $config->get('HTML.Trusted'); // generate @@ -211,24 +243,34 @@ class HTMLPurifier_HTMLModuleManager if (is_array($lookup)) { foreach ($modules as $k => $m) { - if (isset($special_cases[$m])) continue; - if (!isset($lookup[$m])) unset($modules[$k]); + if (isset($special_cases[$m])) { + continue; + } + if (!isset($lookup[$m])) { + unset($modules[$k]); + } } } - // add proprietary module (this gets special treatment because - // it is completely removed from doctypes, etc.) + // custom modules if ($config->get('HTML.Proprietary')) { $modules[] = 'Proprietary'; } - - // add SafeObject/Safeembed modules if ($config->get('HTML.SafeObject')) { $modules[] = 'SafeObject'; } if ($config->get('HTML.SafeEmbed')) { $modules[] = 'SafeEmbed'; } + if ($config->get('HTML.SafeScripting') !== array()) { + $modules[] = 'SafeScripting'; + } + if ($config->get('HTML.Nofollow')) { + $modules[] = 'Nofollow'; + } + if ($config->get('HTML.TargetBlank')) { + $modules[] = 'TargetBlank'; + } // merge in custom modules $modules = array_merge($modules, $this->userModules); @@ -246,7 +288,7 @@ class HTMLPurifier_HTMLModuleManager // prepare any injectors foreach ($this->modules as $module) { $n = array(); - foreach ($module->info_injector as $i => $injector) { + foreach ($module->info_injector as $injector) { if (!is_object($injector)) { $class = "HTMLPurifier_Injector_$injector"; $injector = new $class; @@ -285,7 +327,8 @@ class HTMLPurifier_HTMLModuleManager * Takes a module and adds it to the active module collection, * registering it if necessary. */ - public function processModule($module) { + public function processModule($module) + { if (!isset($this->registeredModules[$module]) || is_object($module)) { $this->registerModule($module); } @@ -296,13 +339,17 @@ class HTMLPurifier_HTMLModuleManager * Retrieves merged element definitions. * @return Array of HTMLPurifier_ElementDef */ - public function getElements() { - + public function getElements() + { $elements = array(); foreach ($this->modules as $module) { - if (!$this->trusted && !$module->safe) continue; + if (!$this->trusted && !$module->safe) { + continue; + } foreach ($module->info as $name => $v) { - if (isset($elements[$name])) continue; + if (isset($elements[$name])) { + continue; + } $elements[$name] = $this->getElement($name); } } @@ -310,7 +357,9 @@ class HTMLPurifier_HTMLModuleManager // remove dud elements, this happens when an element that // appeared to be safe actually wasn't foreach ($elements as $n => $v) { - if ($v === false) unset($elements[$n]); + if ($v === false) { + unset($elements[$n]); + } } return $elements; @@ -319,28 +368,29 @@ class HTMLPurifier_HTMLModuleManager /** * Retrieves a single merged element definition - * @param $name Name of element - * @param $trusted Boolean trusted overriding parameter: set to true + * @param string $name Name of element + * @param bool $trusted Boolean trusted overriding parameter: set to true * if you want the full version of an element - * @return Merged HTMLPurifier_ElementDef + * @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef * @note You may notice that modules are getting iterated over twice (once * in getElements() and once here). This * is because */ - public function getElement($name, $trusted = null) { - + public function getElement($name, $trusted = null) + { if (!isset($this->elementLookup[$name])) { return false; } // setup global state variables $def = false; - if ($trusted === null) $trusted = $this->trusted; + if ($trusted === null) { + $trusted = $this->trusted; + } // iterate through each module that has registered itself to this // element - foreach($this->elementLookup[$name] as $module_name) { - + foreach ($this->elementLookup[$name] as $module_name) { $module = $this->modules[$module_name]; // refuse to create/merge from a module that is deemed unsafe-- @@ -364,6 +414,13 @@ class HTMLPurifier_HTMLModuleManager // :TODO: // non-standalone definitions that don't have a standalone // to merge into could be deferred to the end + // HOWEVER, it is perfectly valid for a non-standalone + // definition to lack a standalone definition, even + // after all processing: this allows us to safely + // specify extra attributes for elements that may not be + // enabled all in one place. In particular, this might + // be the case for trusted elements. WARNING: care must + // be taken that the /extra/ definitions are all safe. continue; } @@ -385,7 +442,9 @@ class HTMLPurifier_HTMLModuleManager // This can occur if there is a blank definition, but no base to // mix it in with - if (!$def) return false; + if (!$def) { + return false; + } // add information on required attributes foreach ($def->attr as $attr_name => $attr_def) { @@ -393,11 +452,8 @@ class HTMLPurifier_HTMLModuleManager $def->required_attr[] = $attr_name; } } - return $def; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/IDAccumulator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php similarity index 66% rename from library/HTMLPurifier/IDAccumulator.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php index 73215295a5..65c902c076 100644 --- a/library/HTMLPurifier/IDAccumulator.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php @@ -17,11 +17,12 @@ class HTMLPurifier_IDAccumulator /** * Builds an IDAccumulator, also initializing the default blacklist - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @return Fully initialized HTMLPurifier_IDAccumulator + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context + * @return HTMLPurifier_IDAccumulator Fully initialized HTMLPurifier_IDAccumulator */ - public static function build($config, $context) { + public static function build($config, $context) + { $id_accumulator = new HTMLPurifier_IDAccumulator(); $id_accumulator->load($config->get('Attr.IDBlacklist')); return $id_accumulator; @@ -29,11 +30,14 @@ class HTMLPurifier_IDAccumulator /** * Add an ID to the lookup table. - * @param $id ID to be added. - * @return Bool status, true if success, false if there's a dupe + * @param string $id ID to be added. + * @return bool status, true if success, false if there's a dupe */ - public function add($id) { - if (isset($this->ids[$id])) return false; + public function add($id) + { + if (isset($this->ids[$id])) { + return false; + } return $this->ids[$id] = true; } @@ -42,12 +46,12 @@ class HTMLPurifier_IDAccumulator * @param $array_of_ids Array of IDs to load * @note This function doesn't care about duplicates */ - public function load($array_of_ids) { + public function load($array_of_ids) + { foreach ($array_of_ids as $id) { $this->ids[$id] = true; } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Injector.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php similarity index 55% rename from library/HTMLPurifier/Injector.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php index 5922f81305..5060eef9e2 100644 --- a/library/HTMLPurifier/Injector.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php @@ -17,64 +17,71 @@ abstract class HTMLPurifier_Injector { /** - * Advisory name of injector, this is for friendly error messages + * Advisory name of injector, this is for friendly error messages. + * @type string */ public $name; /** - * Instance of HTMLPurifier_HTMLDefinition + * @type HTMLPurifier_HTMLDefinition */ protected $htmlDefinition; /** * Reference to CurrentNesting variable in Context. This is an array * list of tokens that we are currently "inside" + * @type array */ protected $currentNesting; /** - * Reference to InputTokens variable in Context. This is an array - * list of the input tokens that are being processed. + * Reference to current token. + * @type HTMLPurifier_Token */ - protected $inputTokens; + protected $currentToken; /** - * Reference to InputIndex variable in Context. This is an integer - * array index for $this->inputTokens that indicates what token - * is currently being processed. + * Reference to InputZipper variable in Context. + * @type HTMLPurifier_Zipper */ - protected $inputIndex; + protected $inputZipper; /** * Array of elements and attributes this injector creates and therefore * need to be allowed by the definition. Takes form of * array('element' => array('attr', 'attr2'), 'element2') + * @type array */ public $needed = array(); /** - * Index of inputTokens to rewind to. + * Number of elements to rewind backwards (relative). + * @type bool|int */ - protected $rewind = false; + protected $rewindOffset = false; /** * Rewind to a spot to re-perform processing. This is useful if you * deleted a node, and now need to see if this change affected any * earlier nodes. Rewinding does not affect other injectors, and can * result in infinite loops if not used carefully. + * @param bool|int $offset * @warning HTML Purifier will prevent you from fast-forwarding with this * function. */ - public function rewind($index) { - $this->rewind = $index; + public function rewindOffset($offset) + { + $this->rewindOffset = $offset; } /** - * Retrieves rewind, and then unsets it. + * Retrieves rewind offset, and then unsets it. + * @return bool|int */ - public function getRewind() { - $r = $this->rewind; - $this->rewind = false; + public function getRewindOffset() + { + $r = $this->rewindOffset; + $this->rewindOffset = false; return $r; } @@ -83,20 +90,23 @@ abstract class HTMLPurifier_Injector * this allows references to important variables to be made within * the injector. This function also checks if the HTML environment * will work with the Injector (see checkNeeded()). - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @return Boolean false if success, string of missing needed element/attribute if failure + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string Boolean false if success, string of missing needed element/attribute if failure */ - public function prepare($config, $context) { + public function prepare($config, $context) + { $this->htmlDefinition = $config->getHTMLDefinition(); // Even though this might fail, some unit tests ignore this and // still test checkNeeded, so be careful. Maybe get rid of that // dependency. $result = $this->checkNeeded($config); - if ($result !== false) return $result; + if ($result !== false) { + return $result; + } $this->currentNesting =& $context->get('CurrentNesting'); - $this->inputTokens =& $context->get('InputTokens'); - $this->inputIndex =& $context->get('InputIndex'); + $this->currentToken =& $context->get('CurrentToken'); + $this->inputZipper =& $context->get('InputZipper'); return false; } @@ -104,18 +114,26 @@ abstract class HTMLPurifier_Injector * This function checks if the HTML environment * will work with the Injector: if p tags are not allowed, the * Auto-Paragraphing injector should not be enabled. - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @return Boolean false if success, string of missing needed element/attribute if failure + * @param HTMLPurifier_Config $config + * @return bool|string Boolean false if success, string of missing needed element/attribute if failure */ - public function checkNeeded($config) { + public function checkNeeded($config) + { $def = $config->getHTMLDefinition(); foreach ($this->needed as $element => $attributes) { - if (is_int($element)) $element = $attributes; - if (!isset($def->info[$element])) return $element; - if (!is_array($attributes)) continue; + if (is_int($element)) { + $element = $attributes; + } + if (!isset($def->info[$element])) { + return $element; + } + if (!is_array($attributes)) { + continue; + } foreach ($attributes as $name) { - if (!isset($def->info[$element]->attr[$name])) return "$element.$name"; + if (!isset($def->info[$element]->attr[$name])) { + return "$element.$name"; + } } } return false; @@ -123,10 +141,11 @@ abstract class HTMLPurifier_Injector /** * Tests if the context node allows a certain element - * @param $name Name of element to test for - * @return True if element is allowed, false if it is not + * @param string $name Name of element to test for + * @return bool True if element is allowed, false if it is not */ - public function allowsElement($name) { + public function allowsElement($name) + { if (!empty($this->currentNesting)) { $parent_token = array_pop($this->currentNesting); $this->currentNesting[] = $parent_token; @@ -141,7 +160,9 @@ abstract class HTMLPurifier_Injector for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) { $node = $this->currentNesting[$i]; $def = $this->htmlDefinition->info[$node->name]; - if (isset($def->excludes[$name])) return false; + if (isset($def->excludes[$name])) { + return false; + } } return true; } @@ -151,14 +172,22 @@ abstract class HTMLPurifier_Injector * you reach the end of the input tokens. * @warning Please prevent previous references from interfering with this * functions by setting $i = null beforehand! - * @param &$i Current integer index variable for inputTokens - * @param &$current Current token variable. Do NOT use $token, as that variable is also a reference + * @param int $i Current integer index variable for inputTokens + * @param HTMLPurifier_Token $current Current token variable. + * Do NOT use $token, as that variable is also a reference + * @return bool */ - protected function forward(&$i, &$current) { - if ($i === null) $i = $this->inputIndex + 1; - else $i++; - if (!isset($this->inputTokens[$i])) return false; - $current = $this->inputTokens[$i]; + protected function forward(&$i, &$current) + { + if ($i === null) { + $i = count($this->inputZipper->back) - 1; + } else { + $i--; + } + if ($i < 0) { + return false; + } + $current = $this->inputZipper->back[$i]; return true; } @@ -166,14 +195,27 @@ abstract class HTMLPurifier_Injector * Similar to _forward, but accepts a third parameter $nesting (which * should be initialized at 0) and stops when we hit the end tag * for the node $this->inputIndex starts in. + * @param int $i Current integer index variable for inputTokens + * @param HTMLPurifier_Token $current Current token variable. + * Do NOT use $token, as that variable is also a reference + * @param int $nesting + * @return bool */ - protected function forwardUntilEndToken(&$i, &$current, &$nesting) { + protected function forwardUntilEndToken(&$i, &$current, &$nesting) + { $result = $this->forward($i, $current); - if (!$result) return false; - if ($nesting === null) $nesting = 0; - if ($current instanceof HTMLPurifier_Token_Start) $nesting++; - elseif ($current instanceof HTMLPurifier_Token_End) { - if ($nesting <= 0) return false; + if (!$result) { + return false; + } + if ($nesting === null) { + $nesting = 0; + } + if ($current instanceof HTMLPurifier_Token_Start) { + $nesting++; + } elseif ($current instanceof HTMLPurifier_Token_End) { + if ($nesting <= 0) { + return false; + } $nesting--; } return true; @@ -184,56 +226,56 @@ abstract class HTMLPurifier_Injector * you reach the beginning of input tokens. * @warning Please prevent previous references from interfering with this * functions by setting $i = null beforehand! - * @param &$i Current integer index variable for inputTokens - * @param &$current Current token variable. Do NOT use $token, as that variable is also a reference + * @param int $i Current integer index variable for inputTokens + * @param HTMLPurifier_Token $current Current token variable. + * Do NOT use $token, as that variable is also a reference + * @return bool */ - protected function backward(&$i, &$current) { - if ($i === null) $i = $this->inputIndex - 1; - else $i--; - if ($i < 0) return false; - $current = $this->inputTokens[$i]; + protected function backward(&$i, &$current) + { + if ($i === null) { + $i = count($this->inputZipper->front) - 1; + } else { + $i--; + } + if ($i < 0) { + return false; + } + $current = $this->inputZipper->front[$i]; return true; } - /** - * Initializes the iterator at the current position. Use in a do {} while; - * loop to force the _forward and _backward functions to start at the - * current location. - * @warning Please prevent previous references from interfering with this - * functions by setting $i = null beforehand! - * @param &$i Current integer index variable for inputTokens - * @param &$current Current token variable. Do NOT use $token, as that variable is also a reference - */ - protected function current(&$i, &$current) { - if ($i === null) $i = $this->inputIndex; - $current = $this->inputTokens[$i]; - } - /** * Handler that is called when a text token is processed */ - public function handleText(&$token) {} + public function handleText(&$token) + { + } /** * Handler that is called when a start or empty token is processed */ - public function handleElement(&$token) {} + public function handleElement(&$token) + { + } /** * Handler that is called when an end token is processed */ - public function handleEnd(&$token) { + public function handleEnd(&$token) + { $this->notifyEnd($token); } /** * Notifier that is called when an end token is processed + * @param HTMLPurifier_Token $token Current token variable. * @note This differs from handlers in that the token is read-only * @deprecated */ - public function notifyEnd($token) {} - - + public function notifyEnd($token) + { + } } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Injector/AutoParagraph.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php similarity index 88% rename from library/HTMLPurifier/Injector/AutoParagraph.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php index afa7608924..4afdd128d5 100644 --- a/library/HTMLPurifier/Injector/AutoParagraph.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php @@ -8,17 +8,31 @@ */ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector { - + /** + * @type string + */ public $name = 'AutoParagraph'; + + /** + * @type array + */ public $needed = array('p'); - private function _pStart() { + /** + * @return HTMLPurifier_Token_Start + */ + private function _pStart() + { $par = new HTMLPurifier_Token_Start('p'); $par->armor['MakeWellFormed_TagClosedError'] = true; return $par; } - public function handleText(&$token) { + /** + * @param HTMLPurifier_Token_Text $token + */ + public function handleText(&$token) + { $text = $token->data; // Does the current parent allow <p> tags? if ($this->allowsElement('p')) { @@ -72,11 +86,9 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector // ---- } } - // Is the current parent a <p> tag? - } elseif ( - !empty($this->currentNesting) && - $this->currentNesting[count($this->currentNesting)-1]->name == 'p' - ) { + // Is the current parent a <p> tag? + } elseif (!empty($this->currentNesting) && + $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') { // State 3.1: ...<p>PAR1 // ---- @@ -84,7 +96,7 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector // ------------ $token = array(); $this->_splitText($text, $token); - // Abort! + // Abort! } else { // State 4.1: ...<b>PAR1 // ---- @@ -94,7 +106,11 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector } } - public function handleElement(&$token) { + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { // We don't have to check if we're already in a <p> tag for block // tokens, because the tag would have been autoclosed by MakeWellFormed. if ($this->allowsElement('p')) { @@ -102,7 +118,6 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector if ($this->_isInline($token)) { // State 1: <div>...<b> // --- - // Check if this token is adjacent to the parent token // (seek backwards until token isn't whitespace) $i = null; @@ -110,31 +125,24 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector if (!$prev instanceof HTMLPurifier_Token_Start) { // Token wasn't adjacent - - if ( - $prev instanceof HTMLPurifier_Token_Text && + if ($prev instanceof HTMLPurifier_Token_Text && substr($prev->data, -2) === "\n\n" ) { // State 1.1.4: <div><p>PAR1</p>\n\n<b> // --- - // Quite frankly, this should be handled by splitText $token = array($this->_pStart(), $token); } else { // State 1.1.1: <div><p>PAR1</p><b> // --- - // State 1.1.2: <div><br /><b> // --- - // State 1.1.3: <div>PAR<b> // --- } - } else { // State 1.2.1: <div><b> // --- - // Lookahead to see if <p> is needed. if ($this->_pLookAhead()) { // State 1.3.1: <div><b>PAR1\n\nPAR2 @@ -166,24 +174,20 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector $i = null; if ($this->backward($i, $prev)) { - if ( - !$prev instanceof HTMLPurifier_Token_Text - ) { + if (!$prev instanceof HTMLPurifier_Token_Text) { // State 3.1.1: ...</p>{p}<b> // --- - // State 3.2.1: ...</p><div> // ----- - - if (!is_array($token)) $token = array($token); + if (!is_array($token)) { + $token = array($token); + } array_unshift($token, new HTMLPurifier_Token_Text("\n\n")); } else { // State 3.1.2: ...</p>\n\n{p}<b> // --- - // State 3.2.2: ...</p>\n\n<div> // ----- - // Note: PAR<ELEM> cannot occur because PAR would have been // wrapped in <p> tags. } @@ -192,7 +196,6 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector } else { // State 2.2: <ul><li> // ---- - // State 2.4: <p><b> // --- } @@ -201,18 +204,17 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector /** * Splits up a text in paragraph tokens and appends them * to the result stream that will replace the original - * @param $data String text data that will be processed + * @param string $data String text data that will be processed * into paragraphs - * @param $result Reference to array of tokens that the + * @param HTMLPurifier_Token[] $result Reference to array of tokens that the * tags will be appended onto - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context */ - private function _splitText($data, &$result) { + private function _splitText($data, &$result) + { $raw_paragraphs = explode("\n\n", $data); - $paragraphs = array(); // without empty paragraphs + $paragraphs = array(); // without empty paragraphs $needs_start = false; - $needs_end = false; + $needs_end = false; $c = count($raw_paragraphs); if ($c == 1) { @@ -285,26 +287,33 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector array_pop($result); // removes \n\n array_pop($result); // removes </p> } - } /** * Returns true if passed token is inline (and, ergo, allowed in * paragraph tags) + * @param HTMLPurifier_Token $token + * @return bool */ - private function _isInline($token) { + private function _isInline($token) + { return isset($this->htmlDefinition->info['p']->child->elements[$token->name]); } /** * Looks ahead in the token list and determines whether or not we need * to insert a <p> tag. + * @return bool */ - private function _pLookAhead() { - $this->current($i, $current); - if ($current instanceof HTMLPurifier_Token_Start) $nesting = 1; - else $nesting = 0; + private function _pLookAhead() + { + if ($this->currentToken instanceof HTMLPurifier_Token_Start) { + $nesting = 1; + } else { + $nesting = 0; + } $ok = false; + $i = null; while ($this->forwardUntilEndToken($i, $current, $nesting)) { $result = $this->_checkNeedsP($current); if ($result !== null) { @@ -318,9 +327,12 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector /** * Determines if a particular token requires an earlier inline token * to get a paragraph. This should be used with _forwardUntilEndToken + * @param HTMLPurifier_Token $current + * @return bool */ - private function _checkNeedsP($current) { - if ($current instanceof HTMLPurifier_Token_Start){ + private function _checkNeedsP($current) + { + if ($current instanceof HTMLPurifier_Token_Start) { if (!$this->_isInline($current)) { // <div>PAR1<div> // ---- @@ -339,7 +351,6 @@ class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector } return null; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Injector/DisplayLinkURI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php similarity index 64% rename from library/HTMLPurifier/Injector/DisplayLinkURI.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php index 9dce9bd085..c19b1bc271 100644 --- a/library/HTMLPurifier/Injector/DisplayLinkURI.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php @@ -5,15 +5,29 @@ */ class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector { - + /** + * @type string + */ public $name = 'DisplayLinkURI'; + + /** + * @type array + */ public $needed = array('a'); - public function handleElement(&$token) { + /** + * @param $token + */ + public function handleElement(&$token) + { } - public function handleEnd(&$token) { - if (isset($token->start->attr['href'])){ + /** + * @param HTMLPurifier_Token $token + */ + public function handleEnd(&$token) + { + if (isset($token->start->attr['href'])) { $url = $token->start->attr['href']; unset($token->start->attr['href']); $token = array($token, new HTMLPurifier_Token_Text(" ($url)")); diff --git a/library/HTMLPurifier/Injector/Linkify.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php similarity index 72% rename from library/HTMLPurifier/Injector/Linkify.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php index 296dac2829..069708c250 100644 --- a/library/HTMLPurifier/Injector/Linkify.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php @@ -5,12 +5,24 @@ */ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector { - + /** + * @type string + */ public $name = 'Linkify'; + + /** + * @type array + */ public $needed = array('a' => array('href')); - public function handleText(&$token) { - if (!$this->allowsElement('a')) return; + /** + * @param HTMLPurifier_Token $token + */ + public function handleText(&$token) + { + if (!$this->allowsElement('a')) { + return; + } if (strpos($token->data, '://') === false) { // our really quick heuristic failed, abort @@ -21,7 +33,8 @@ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector // there is/are URL(s). Let's split the string: // Note: this regex is extremely permissive - $bits = preg_split('#((?:https?|ftp)://[^\s\'"<>()]+)#S', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); + $bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); + $token = array(); @@ -30,7 +43,9 @@ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector // $l = is link for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) { if (!$l) { - if ($bits[$i] === '') continue; + if ($bits[$i] === '') { + continue; + } $token[] = new HTMLPurifier_Token_Text($bits[$i]); } else { $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i])); @@ -38,9 +53,7 @@ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector $token[] = new HTMLPurifier_Token_End('a'); } } - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Injector/PurifierLinkify.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php similarity index 58% rename from library/HTMLPurifier/Injector/PurifierLinkify.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php index ad2455a91c..cb9046f334 100644 --- a/library/HTMLPurifier/Injector/PurifierLinkify.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php @@ -6,19 +6,43 @@ */ class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector { - + /** + * @type string + */ public $name = 'PurifierLinkify'; + + /** + * @type string + */ public $docURL; + + /** + * @type array + */ public $needed = array('a' => array('href')); - public function prepare($config, $context) { + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function prepare($config, $context) + { $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL'); return parent::prepare($config, $context); } - public function handleText(&$token) { - if (!$this->allowsElement('a')) return; - if (strpos($token->data, '%') === false) return; + /** + * @param HTMLPurifier_Token $token + */ + public function handleText(&$token) + { + if (!$this->allowsElement('a')) { + return; + } + if (strpos($token->data, '%') === false) { + return; + } $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); $token = array(); @@ -28,18 +52,20 @@ class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector // $l = is link for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) { if (!$l) { - if ($bits[$i] === '') continue; + if ($bits[$i] === '') { + continue; + } $token[] = new HTMLPurifier_Token_Text($bits[$i]); } else { - $token[] = new HTMLPurifier_Token_Start('a', - array('href' => str_replace('%s', $bits[$i], $this->docURL))); + $token[] = new HTMLPurifier_Token_Start( + 'a', + array('href' => str_replace('%s', $bits[$i], $this->docURL)) + ); $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]); $token[] = new HTMLPurifier_Token_End('a'); } } - } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php new file mode 100644 index 0000000000..01353ff1d5 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php @@ -0,0 +1,106 @@ +<?php + +class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector +{ + /** + * @type HTMLPurifier_Context + */ + private $context; + + /** + * @type HTMLPurifier_Config + */ + private $config; + + /** + * @type HTMLPurifier_AttrValidator + */ + private $attrValidator; + + /** + * @type bool + */ + private $removeNbsp; + + /** + * @type bool + */ + private $removeNbspExceptions; + + /** + * Cached contents of %AutoFormat.RemoveEmpty.Predicate + * @type array + */ + private $exclude; + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return void + */ + public function prepare($config, $context) + { + parent::prepare($config, $context); + $this->config = $config; + $this->context = $context; + $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp'); + $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions'); + $this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate'); + $this->attrValidator = new HTMLPurifier_AttrValidator(); + } + + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { + if (!$token instanceof HTMLPurifier_Token_Start) { + return; + } + $next = false; + $deleted = 1; // the current tag + for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) { + $next = $this->inputZipper->back[$i]; + if ($next instanceof HTMLPurifier_Token_Text) { + if ($next->is_whitespace) { + continue; + } + if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) { + $plain = str_replace("\xC2\xA0", "", $next->data); + $isWsOrNbsp = $plain === '' || ctype_space($plain); + if ($isWsOrNbsp) { + continue; + } + } + } + break; + } + if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) { + $this->attrValidator->validateToken($token, $this->config, $this->context); + $token->armor['ValidateAttributes'] = true; + if (isset($this->exclude[$token->name])) { + $r = true; + foreach ($this->exclude[$token->name] as $elem) { + if (!isset($token->attr[$elem])) $r = false; + } + if ($r) return; + } + if (isset($token->attr['id']) || isset($token->attr['name'])) { + return; + } + $token = $deleted + 1; + for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) { + $prev = $this->inputZipper->front[$b]; + if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) { + continue; + } + break; + } + // This is safe because we removed the token that triggered this. + $this->rewindOffset($b+$deleted); + return; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php similarity index 74% rename from library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php index b21313470e..9ee7aa84d7 100644 --- a/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php @@ -5,25 +5,45 @@ */ class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector { + /** + * @type string + */ public $name = 'RemoveSpansWithoutAttributes'; + + /** + * @type array + */ public $needed = array('span'); + /** + * @type HTMLPurifier_AttrValidator + */ private $attrValidator; /** - * Used by AttrValidator + * Used by AttrValidator. + * @type HTMLPurifier_Config */ private $config; + + /** + * @type HTMLPurifier_Context + */ private $context; - public function prepare($config, $context) { + public function prepare($config, $context) + { $this->attrValidator = new HTMLPurifier_AttrValidator(); $this->config = $config; $this->context = $context; return parent::prepare($config, $context); } - public function handleElement(&$token) { + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) { return; } @@ -39,8 +59,8 @@ class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_In } $nesting = 0; - $spanContentTokens = array(); - while ($this->forwardUntilEndToken($i, $current, $nesting)) {} + while ($this->forwardUntilEndToken($i, $current, $nesting)) { + } if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') { // Mark closing span tag for deletion @@ -50,7 +70,11 @@ class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_In } } - public function handleEnd(&$token) { + /** + * @param HTMLPurifier_Token $token + */ + public function handleEnd(&$token) + { if ($token->markForDeletion) { $token = false; } diff --git a/library/HTMLPurifier/Injector/SafeObject.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php similarity index 78% rename from library/HTMLPurifier/Injector/SafeObject.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php index 9e178ce01a..3d17e07af2 100644 --- a/library/HTMLPurifier/Injector/SafeObject.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php @@ -6,29 +6,61 @@ */ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector { + /** + * @type string + */ public $name = 'SafeObject'; + + /** + * @type array + */ public $needed = array('object', 'param'); + /** + * @type array + */ protected $objectStack = array(); - protected $paramStack = array(); - // Keep this synchronized with AttrTransform/SafeParam.php + /** + * @type array + */ + protected $paramStack = array(); + + /** + * Keep this synchronized with AttrTransform/SafeParam.php. + * @type array + */ protected $addParam = array( 'allowScriptAccess' => 'never', 'allowNetworking' => 'internal', ); + + /** + * @type array + */ protected $allowedParam = array( 'wmode' => true, 'movie' => true, 'flashvars' => true, 'src' => true, + 'allowFullScreen' => true, // if omitted, assume to be 'false' ); - public function prepare($config, $context) { + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return void + */ + public function prepare($config, $context) + { parent::prepare($config, $context); } - public function handleElement(&$token) { + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { if ($token->name == 'object') { $this->objectStack[] = $token; $this->paramStack[] = array(); @@ -50,16 +82,15 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector // attribute, which we need if a type is specified. This is // *very* Flash specific. if (!isset($this->objectStack[$i]->attr['data']) && - ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')) { + ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src') + ) { $this->objectStack[$i]->attr['data'] = $token->attr['value']; } // Check if the parameter is the correct value but has not // already been added - if ( - !isset($this->paramStack[$i][$n]) && + if (!isset($this->paramStack[$i][$n]) && isset($this->addParam[$n]) && - $token->attr['name'] === $this->addParam[$n] - ) { + $token->attr['name'] === $this->addParam[$n]) { // keep token, and add to param stack $this->paramStack[$i][$n] = true; } elseif (isset($this->allowedParam[$n])) { @@ -75,7 +106,8 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector } } - public function handleEnd(&$token) { + public function handleEnd(&$token) + { // This is the WRONG way of handling the object and param stacks; // we should be inserting them directly on the relevant object tokens // so that the global stack handling handles it. @@ -84,7 +116,6 @@ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector array_pop($this->paramStack); } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Language.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language.php similarity index 67% rename from library/HTMLPurifier/Language.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Language.php index 3e2be03b58..65277dd43c 100644 --- a/library/HTMLPurifier/Language.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language.php @@ -8,22 +8,26 @@ class HTMLPurifier_Language { /** - * ISO 639 language code of language. Prefers shortest possible version + * ISO 639 language code of language. Prefers shortest possible version. + * @type string */ public $code = 'en'; /** - * Fallback language code + * Fallback language code. + * @type bool|string */ public $fallback = false; /** - * Array of localizable messages + * Array of localizable messages. + * @type array */ public $messages = array(); /** - * Array of localizable error codes + * Array of localizable error codes. + * @type array */ public $errorNames = array(); @@ -31,21 +35,33 @@ class HTMLPurifier_Language * True if no message file was found for this language, so English * is being used instead. Check this if you'd like to notify the * user that they've used a non-supported language. + * @type bool */ public $error = false; /** * Has the language object been loaded yet? + * @type bool * @todo Make it private, fix usage in HTMLPurifier_LanguageTest */ public $_loaded = false; /** - * Instances of HTMLPurifier_Config and HTMLPurifier_Context + * @type HTMLPurifier_Config */ - protected $config, $context; + protected $config; - public function __construct($config, $context) { + /** + * @type HTMLPurifier_Context + */ + protected $context; + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + public function __construct($config, $context) + { $this->config = $config; $this->context = $context; } @@ -54,8 +70,11 @@ class HTMLPurifier_Language * Loads language object with necessary info from factory cache * @note This is a lazy loader */ - public function load() { - if ($this->_loaded) return; + public function load() + { + if ($this->_loaded) { + return; + } $factory = HTMLPurifier_LanguageFactory::instance(); $factory->loadLanguage($this->code); foreach ($factory->keys as $key) { @@ -66,31 +85,43 @@ class HTMLPurifier_Language /** * Retrieves a localised message. - * @param $key string identifier of message + * @param string $key string identifier of message * @return string localised message */ - public function getMessage($key) { - if (!$this->_loaded) $this->load(); - if (!isset($this->messages[$key])) return "[$key]"; + public function getMessage($key) + { + if (!$this->_loaded) { + $this->load(); + } + if (!isset($this->messages[$key])) { + return "[$key]"; + } return $this->messages[$key]; } /** * Retrieves a localised error name. - * @param $int integer error number, corresponding to PHP's error - * reporting + * @param int $int error number, corresponding to PHP's error reporting * @return string localised message */ - public function getErrorName($int) { - if (!$this->_loaded) $this->load(); - if (!isset($this->errorNames[$int])) return "[Error: $int]"; + public function getErrorName($int) + { + if (!$this->_loaded) { + $this->load(); + } + if (!isset($this->errorNames[$int])) { + return "[Error: $int]"; + } return $this->errorNames[$int]; } /** * Converts an array list into a string readable representation + * @param array $array + * @return string */ - public function listify($array) { + public function listify($array) + { $sep = $this->getMessage('Item separator'); $sep_last = $this->getMessage('Item separator last'); $ret = ''; @@ -108,15 +139,20 @@ class HTMLPurifier_Language /** * Formats a localised message with passed parameters - * @param $key string identifier of message - * @param $args Parameters to substitute in + * @param string $key string identifier of message + * @param array $args Parameters to substitute in * @return string localised message * @todo Implement conditionals? Right now, some messages make * reference to line numbers, but those aren't always available */ - public function formatMessage($key, $args = array()) { - if (!$this->_loaded) $this->load(); - if (!isset($this->messages[$key])) return "[$key]"; + public function formatMessage($key, $args = array()) + { + if (!$this->_loaded) { + $this->load(); + } + if (!isset($this->messages[$key])) { + return "[$key]"; + } $raw = $this->messages[$key]; $subst = array(); $generator = false; @@ -124,9 +160,15 @@ class HTMLPurifier_Language if (is_object($value)) { if ($value instanceof HTMLPurifier_Token) { // factor this out some time - if (!$generator) $generator = $this->context->get('Generator'); - if (isset($value->name)) $subst['$'.$i.'.Name'] = $value->name; - if (isset($value->data)) $subst['$'.$i.'.Data'] = $value->data; + if (!$generator) { + $generator = $this->context->get('Generator'); + } + if (isset($value->name)) { + $subst['$'.$i.'.Name'] = $value->name; + } + if (isset($value->data)) { + $subst['$'.$i.'.Data'] = $value->data; + } $subst['$'.$i.'.Compact'] = $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value); // a more complex algorithm for compact representation @@ -157,7 +199,6 @@ class HTMLPurifier_Language } return strtr($raw, $subst); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Language/classes/en-x-test.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php similarity index 97% rename from library/HTMLPurifier/Language/classes/en-x-test.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php index d52fcb7ac1..8828f5cded 100644 --- a/library/HTMLPurifier/Language/classes/en-x-test.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php @@ -4,9 +4,6 @@ class HTMLPurifier_Language_en_x_test extends HTMLPurifier_Language { - - - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Language/messages/en-x-test.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php similarity index 100% rename from library/HTMLPurifier/Language/messages/en-x-test.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-test.php diff --git a/library/HTMLPurifier/Language/messages/en-x-testmini.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php similarity index 100% rename from library/HTMLPurifier/Language/messages/en-x-testmini.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php new file mode 100644 index 0000000000..c7f197e1e1 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php @@ -0,0 +1,55 @@ +<?php + +$fallback = false; + +$messages = array( + + 'HTMLPurifier' => 'HTML Purifier', +// for unit testing purposes + 'LanguageFactoryTest: Pizza' => 'Pizza', + 'LanguageTest: List' => '$1', + 'LanguageTest: Hash' => '$1.Keys; $1.Values', + 'Item separator' => ', ', + 'Item separator last' => ' and ', // non-Harvard style + + 'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', + 'ErrorCollector: At line' => ' at line $line', + 'ErrorCollector: Incidental errors' => 'Incidental errors', + 'Lexer: Unclosed comment' => 'Unclosed comment', + 'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', + 'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', + 'Lexer: Missing attribute key' => 'Attribute declaration has no key', + 'Lexer: Missing end quote' => 'Attribute declaration has no end quote', + 'Lexer: Extracted body' => 'Removed document metadata tags', + 'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized', + 'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1', + 'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text', + 'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed', + 'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed', + 'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed', + 'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end', + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed', + 'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens', + 'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized', + 'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document', + 'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed', + 'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element', + 'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model', + 'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed', + 'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys', + 'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed', +); + +$errorNames = array( + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_NOTICE => 'Notice' +); + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/LanguageFactory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php similarity index 75% rename from library/HTMLPurifier/LanguageFactory.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php index 134ef8c745..4e35272d87 100644 --- a/library/HTMLPurifier/LanguageFactory.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php @@ -11,50 +11,53 @@ class HTMLPurifier_LanguageFactory { /** - * Cache of language code information used to load HTMLPurifier_Language objects + * Cache of language code information used to load HTMLPurifier_Language objects. * Structure is: $factory->cache[$language_code][$key] = $value - * @value array map + * @type array */ public $cache; /** * Valid keys in the HTMLPurifier_Language object. Designates which * variables to slurp out of a message file. - * @value array list + * @type array */ public $keys = array('fallback', 'messages', 'errorNames'); /** - * Instance of HTMLPurifier_AttrDef_Lang to validate language codes - * @value object HTMLPurifier_AttrDef_Lang + * Instance to validate language codes. + * @type HTMLPurifier_AttrDef_Lang + * */ protected $validator; /** * Cached copy of dirname(__FILE__), directory of current file without - * trailing slash - * @value string filename + * trailing slash. + * @type string */ protected $dir; /** - * Keys whose contents are a hash map and can be merged - * @value array lookup + * Keys whose contents are a hash map and can be merged. + * @type array */ protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true); /** - * Keys whose contents are a list and can be merged + * Keys whose contents are a list and can be merged. * @value array lookup */ protected $mergeable_keys_list = array(); /** * Retrieve sole instance of the factory. - * @param $prototype Optional prototype to overload sole instance with, + * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with, * or bool true to reset to default factory. + * @return HTMLPurifier_LanguageFactory */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { static $instance = null; if ($prototype !== null) { $instance = $prototype; @@ -69,28 +72,34 @@ class HTMLPurifier_LanguageFactory * Sets up the singleton, much like a constructor * @note Prevents people from getting this outside of the singleton */ - public function setup() { + public function setup() + { $this->validator = new HTMLPurifier_AttrDef_Lang(); $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier'; } /** * Creates a language object, handles class fallbacks - * @param $config Instance of HTMLPurifier_Config - * @param $context Instance of HTMLPurifier_Context - * @param $code Code to override configuration with. Private parameter. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @param bool|string $code Code to override configuration with. Private parameter. + * @return HTMLPurifier_Language */ - public function create($config, $context, $code = false) { - + public function create($config, $context, $code = false) + { // validate language code if ($code === false) { $code = $this->validator->validate( - $config->get('Core.Language'), $config, $context + $config->get('Core.Language'), + $config, + $context ); } else { $code = $this->validator->validate($code, $config, $context); } - if ($code === false) $code = 'en'; // malformed code becomes English + if ($code === false) { + $code = 'en'; // malformed code becomes English + } $pcode = str_replace('-', '_', $code); // make valid PHP classname static $depth = 0; // recursion protection @@ -114,32 +123,34 @@ class HTMLPurifier_LanguageFactory $depth--; } } - $lang->code = $code; - return $lang; - } /** * Returns the fallback language for language * @note Loads the original language into cache - * @param $code string language code + * @param string $code language code + * @return string|bool */ - public function getFallbackFor($code) { + public function getFallbackFor($code) + { $this->loadLanguage($code); return $this->cache[$code]['fallback']; } /** * Loads language into the cache, handles message file and fallbacks - * @param $code string language code + * @param string $code language code */ - public function loadLanguage($code) { + public function loadLanguage($code) + { static $languages_seen = array(); // recursion guard // abort if we've already loaded it - if (isset($this->cache[$code])) return; + if (isset($this->cache[$code])) { + return; + } // generate filename $filename = $this->dir . '/Language/messages/' . $code . '.php'; @@ -162,8 +173,11 @@ class HTMLPurifier_LanguageFactory // infinite recursion guard if (isset($languages_seen[$code])) { - trigger_error('Circular fallback reference in language ' . - $code, E_USER_ERROR); + trigger_error( + 'Circular fallback reference in language ' . + $code, + E_USER_ERROR + ); $fallback = 'en'; } $language_seen[$code] = true; @@ -173,26 +187,23 @@ class HTMLPurifier_LanguageFactory $fallback_cache = $this->cache[$fallback]; // merge fallback with current language - foreach ( $this->keys as $key ) { + foreach ($this->keys as $key) { if (isset($cache[$key]) && isset($fallback_cache[$key])) { if (isset($this->mergeable_keys_map[$key])) { $cache[$key] = $cache[$key] + $fallback_cache[$key]; } elseif (isset($this->mergeable_keys_list[$key])) { - $cache[$key] = array_merge( $fallback_cache[$key], $cache[$key] ); + $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]); } } else { $cache[$key] = $fallback_cache[$key]; } } - } // save to cache for later retrieval $this->cache[$code] = $cache; - return; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Length.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Length.php similarity index 56% rename from library/HTMLPurifier/Length.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Length.php index 8d2a46b7da..bbfbe6624d 100644 --- a/library/HTMLPurifier/Length.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Length.php @@ -9,21 +9,25 @@ class HTMLPurifier_Length /** * String numeric magnitude. + * @type string */ protected $n; /** * String unit. False is permitted if $n = 0. + * @type string|bool */ protected $unit; /** * Whether or not this length is valid. Null if not calculated yet. + * @type bool */ protected $isValid; /** - * Lookup array of units recognized by CSS 2.1 + * Array Lookup array of units recognized by CSS 2.1 + * @type array */ protected static $allowedUnits = array( 'em' => true, 'ex' => true, 'px' => true, 'in' => true, @@ -31,85 +35,126 @@ class HTMLPurifier_Length ); /** - * @param number $n Magnitude - * @param string $u Unit + * @param string $n Magnitude + * @param bool|string $u Unit */ - public function __construct($n = '0', $u = false) { + public function __construct($n = '0', $u = false) + { $this->n = (string) $n; $this->unit = $u !== false ? (string) $u : false; } /** * @param string $s Unit string, like '2em' or '3.4in' + * @return HTMLPurifier_Length * @warning Does not perform validation. */ - static public function make($s) { - if ($s instanceof HTMLPurifier_Length) return $s; + public static function make($s) + { + if ($s instanceof HTMLPurifier_Length) { + return $s; + } $n_length = strspn($s, '1234567890.+-'); $n = substr($s, 0, $n_length); $unit = substr($s, $n_length); - if ($unit === '') $unit = false; + if ($unit === '') { + $unit = false; + } return new HTMLPurifier_Length($n, $unit); } /** * Validates the number and unit. + * @return bool */ - protected function validate() { + protected function validate() + { // Special case: - if ($this->n === '+0' || $this->n === '-0') $this->n = '0'; - if ($this->n === '0' && $this->unit === false) return true; - if (!ctype_lower($this->unit)) $this->unit = strtolower($this->unit); - if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) return false; + if ($this->n === '+0' || $this->n === '-0') { + $this->n = '0'; + } + if ($this->n === '0' && $this->unit === false) { + return true; + } + if (!ctype_lower($this->unit)) { + $this->unit = strtolower($this->unit); + } + if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) { + return false; + } // Hack: $def = new HTMLPurifier_AttrDef_CSS_Number(); $result = $def->validate($this->n, false, false); - if ($result === false) return false; + if ($result === false) { + return false; + } $this->n = $result; return true; } /** * Returns string representation of number. + * @return string */ - public function toString() { - if (!$this->isValid()) return false; + public function toString() + { + if (!$this->isValid()) { + return false; + } return $this->n . $this->unit; } /** * Retrieves string numeric magnitude. + * @return string */ - public function getN() {return $this->n;} + public function getN() + { + return $this->n; + } /** * Retrieves string unit. + * @return string */ - public function getUnit() {return $this->unit;} + public function getUnit() + { + return $this->unit; + } /** * Returns true if this length unit is valid. + * @return bool */ - public function isValid() { - if ($this->isValid === null) $this->isValid = $this->validate(); + public function isValid() + { + if ($this->isValid === null) { + $this->isValid = $this->validate(); + } return $this->isValid; } /** * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal. + * @param HTMLPurifier_Length $l + * @return int * @warning If both values are too large or small, this calculation will * not work properly */ - public function compareTo($l) { - if ($l === false) return false; + public function compareTo($l) + { + if ($l === false) { + return false; + } if ($l->unit !== $this->unit) { $converter = new HTMLPurifier_UnitConverter(); $l = $converter->convert($l, $this->unit); - if ($l === false) return false; + if ($l === false) { + return false; + } } return $this->n - $l->n; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Lexer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php similarity index 63% rename from library/HTMLPurifier/Lexer.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php index b05e115468..43732621dc 100644 --- a/library/HTMLPurifier/Lexer.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php @@ -62,16 +62,20 @@ class HTMLPurifier_Lexer * To specify your own prototype, set %Core.LexerImpl to it. * This change in behavior de-singletonizes the lexer object. * - * @param $config Instance of HTMLPurifier_Config - * @return Concrete lexer. + * @param HTMLPurifier_Config $config + * @return HTMLPurifier_Lexer + * @throws HTMLPurifier_Exception */ - public static function create($config) { - + public static function create($config) + { if (!($config instanceof HTMLPurifier_Config)) { $lexer = $config; - trigger_error("Passing a prototype to - HTMLPurifier_Lexer::create() is deprecated, please instead - use %Core.LexerImpl", E_USER_WARNING); + trigger_error( + "Passing a prototype to + HTMLPurifier_Lexer::create() is deprecated, please instead + use %Core.LexerImpl", + E_USER_WARNING + ); } else { $lexer = $config->get('Core.LexerImpl'); } @@ -84,30 +88,28 @@ class HTMLPurifier_Lexer if (is_object($lexer)) { $inst = $lexer; } else { + if (is_null($lexer)) { + do { + // auto-detection algorithm + if ($needs_tracking) { + $lexer = 'DirectLex'; + break; + } - if (is_null($lexer)) { do { - // auto-detection algorithm - - if ($needs_tracking) { - $lexer = 'DirectLex'; - break; - } - - if ( - class_exists('DOMDocument') && - method_exists('DOMDocument', 'loadHTML') && - !extension_loaded('domxml') - ) { - // check for DOM support, because while it's part of the - // core, it can be disabled compile time. Also, the PECL - // domxml extension overrides the default DOM, and is evil - // and nasty and we shan't bother to support it - $lexer = 'DOMLex'; - } else { - $lexer = 'DirectLex'; - } - - } while(0); } // do..while so we can break + if (class_exists('DOMDocument') && + method_exists('DOMDocument', 'loadHTML') && + !extension_loaded('domxml') + ) { + // check for DOM support, because while it's part of the + // core, it can be disabled compile time. Also, the PECL + // domxml extension overrides the default DOM, and is evil + // and nasty and we shan't bother to support it + $lexer = 'DOMLex'; + } else { + $lexer = 'DirectLex'; + } + } while (0); + } // do..while so we can break // instantiate recognized string names switch ($lexer) { @@ -121,16 +123,24 @@ class HTMLPurifier_Lexer $inst = new HTMLPurifier_Lexer_PH5P(); break; default: - throw new HTMLPurifier_Exception("Cannot instantiate unrecognized Lexer type " . htmlspecialchars($lexer)); + throw new HTMLPurifier_Exception( + "Cannot instantiate unrecognized Lexer type " . + htmlspecialchars($lexer) + ); } } - if (!$inst) throw new HTMLPurifier_Exception('No lexer was instantiated'); + if (!$inst) { + throw new HTMLPurifier_Exception('No lexer was instantiated'); + } // once PHP DOM implements native line numbers, or we // hack out something using XSLT, remove this stipulation if ($needs_tracking && !$inst->tracksLineNumbers) { - throw new HTMLPurifier_Exception('Cannot use lexer that does not support line numbers with Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'); + throw new HTMLPurifier_Exception( + 'Cannot use lexer that does not support line numbers with ' . + 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)' + ); } return $inst; @@ -139,23 +149,25 @@ class HTMLPurifier_Lexer // -- CONVENIENCE MEMBERS --------------------------------------------- - public function __construct() { + public function __construct() + { $this->_entity_parser = new HTMLPurifier_EntityParser(); } /** * Most common entity to raw value conversion table for special entities. + * @type array */ protected $_special_entity2str = - array( - '"' => '"', - '&' => '&', - '<' => '<', - '>' => '>', - ''' => "'", - ''' => "'", - ''' => "'" - ); + array( + '"' => '"', + '&' => '&', + '<' => '<', + '>' => '>', + ''' => "'", + ''' => "'", + ''' => "'" + ); /** * Parses special entities into the proper characters. @@ -168,27 +180,33 @@ class HTMLPurifier_Lexer * completely parsed, but that's only because all other entities should * have been handled previously in substituteNonSpecialEntities() * - * @param $string String character data to be parsed. - * @returns Parsed character data. + * @param string $string String character data to be parsed. + * @return string Parsed character data. */ - public function parseData($string) { - + public function parseData($string) + { // following functions require at least one character - if ($string === '') return ''; + if ($string === '') { + return ''; + } // subtracts amps that cannot possibly be escaped $num_amp = substr_count($string, '&') - substr_count($string, '& ') - - ($string[strlen($string)-1] === '&' ? 1 : 0); + ($string[strlen($string) - 1] === '&' ? 1 : 0); - if (!$num_amp) return $string; // abort if no entities + if (!$num_amp) { + return $string; + } // abort if no entities $num_esc_amp = substr_count($string, '&'); $string = strtr($string, $this->_special_entity2str); // code duplication for sake of optimization, see above $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') - - ($string[strlen($string)-1] === '&' ? 1 : 0); + ($string[strlen($string) - 1] === '&' ? 1 : 0); - if ($num_amp_2 <= $num_esc_amp) return $string; + if ($num_amp_2 <= $num_esc_amp) { + return $string; + } // hmm... now we have some uncommon entities. Use the callback. $string = $this->_entity_parser->substituteSpecialEntities($string); @@ -197,21 +215,23 @@ class HTMLPurifier_Lexer /** * Lexes an HTML string into tokens. - * * @param $string String HTML. - * @return HTMLPurifier_Token array representation of HTML. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] array representation of HTML. */ - public function tokenizeHTML($string, $config, $context) { + public function tokenizeHTML($string, $config, $context) + { trigger_error('Call to abstract class', E_USER_ERROR); } /** * Translates CDATA sections into regular sections (through escaping). - * - * @param $string HTML string to process. - * @returns HTML with CDATA sections escaped. + * @param string $string HTML string to process. + * @return string HTML with CDATA sections escaped. */ - protected static function escapeCDATA($string) { + protected static function escapeCDATA($string) + { return preg_replace_callback( '/<!\[CDATA\[(.+?)\]\]>/s', array('HTMLPurifier_Lexer', 'CDATACallback'), @@ -221,8 +241,11 @@ class HTMLPurifier_Lexer /** * Special CDATA case that is especially convoluted for <script> + * @param string $string HTML string to process. + * @return string HTML with CDATA sections escaped. */ - protected static function escapeCommentedCDATA($string) { + protected static function escapeCommentedCDATA($string) + { return preg_replace_callback( '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s', array('HTMLPurifier_Lexer', 'CDATACallback'), @@ -230,16 +253,31 @@ class HTMLPurifier_Lexer ); } + /** + * Special Internet Explorer conditional comments should be removed. + * @param string $string HTML string to process. + * @return string HTML with conditional comments removed. + */ + protected static function removeIEConditional($string) + { + return preg_replace( + '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings + '', + $string + ); + } + /** * Callback function for escapeCDATA() that does the work. * * @warning Though this is public in order to let the callback happen, * calling it directly is not recommended. - * @params $matches PCRE matches array, with index 0 the entire match + * @param array $matches PCRE matches array, with index 0 the entire match * and 1 the inside of the CDATA section. - * @returns Escaped internals of the CDATA section. + * @return string Escaped internals of the CDATA section. */ - protected static function CDATACallback($matches) { + protected static function CDATACallback($matches) + { // not exactly sure why the character set is needed, but whatever return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8'); } @@ -247,13 +285,19 @@ class HTMLPurifier_Lexer /** * Takes a piece of HTML and normalizes it by converting entities, fixing * encoding, extracting bits, and other good stuff. + * @param string $html HTML. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string * @todo Consider making protected */ - public function normalize($html, $config, $context) { - + public function normalize($html, $config, $context) + { // normalize newlines to \n - $html = str_replace("\r\n", "\n", $html); - $html = str_replace("\r", "\n", $html); + if ($config->get('Core.NormalizeNewlines')) { + $html = str_replace("\r\n", "\n", $html); + $html = str_replace("\r", "\n", $html); + } if ($config->get('HTML.Trusted')) { // escape convoluted CDATA @@ -263,6 +307,8 @@ class HTMLPurifier_Lexer // escape CDATA $html = $this->escapeCDATA($html); + $html = $this->removeIEConditional($html); + // extract body from document if applicable if ($config->get('Core.ConvertDocumentToFragment')) { $e = false; @@ -284,6 +330,11 @@ class HTMLPurifier_Lexer // represent non-SGML characters (horror, horror!) $html = HTMLPurifier_Encoder::cleanUTF8($html); + // if processing instructions are to removed, remove them now + if ($config->get('Core.RemoveProcessingInstructions')) { + $html = preg_replace('#<\?.+?\?>#s', '', $html); + } + return $html; } @@ -291,7 +342,8 @@ class HTMLPurifier_Lexer * Takes a string of HTML (fragment or document) and returns the content * @todo Consider making protected */ - public function extractBody($html) { + public function extractBody($html) + { $matches = array(); $result = preg_match('!<body[^>]*>(.*)</body>!is', $html, $matches); if ($result) { @@ -300,7 +352,6 @@ class HTMLPurifier_Lexer return $html; } } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Lexer/DOMLex.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php similarity index 58% rename from library/HTMLPurifier/Lexer/DOMLex.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php index 20dc2ed48c..b818192909 100644 --- a/library/HTMLPurifier/Lexer/DOMLex.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php @@ -27,16 +27,26 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer { + /** + * @type HTMLPurifier_TokenFactory + */ private $factory; - public function __construct() { + public function __construct() + { // setup the factory parent::__construct(); $this->factory = new HTMLPurifier_TokenFactory(); } - public function tokenizeHTML($html, $config, $context) { - + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function tokenizeHTML($html, $config, $context) + { $html = $this->normalize($html, $config, $context); // attempt to armor stray angled brackets that cannot possibly @@ -65,30 +75,66 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer $tokens = array(); $this->tokenizeDOM( $doc->getElementsByTagName('html')->item(0)-> // <html> - getElementsByTagName('body')->item(0)-> // <body> - getElementsByTagName('div')->item(0) // <div> - , $tokens); + getElementsByTagName('body')->item(0), // <body> + $tokens + ); return $tokens; } /** - * Recursive function that tokenizes a node, putting it into an accumulator. - * - * @param $node DOMNode to be tokenized. - * @param $tokens Array-list of already tokenized tokens. - * @param $collect Says whether or start and close are collected, set to - * false at first recursion because it's the implicit DIV - * tag you're dealing with. - * @returns Tokens of node appended to previously passed tokens. + * Iterative function that tokenizes a node, putting it into an accumulator. + * To iterate is human, to recurse divine - L. Peter Deutsch + * @param DOMNode $node DOMNode to be tokenized. + * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. + * @return HTMLPurifier_Token of node appended to previously passed tokens. */ - protected function tokenizeDOM($node, &$tokens, $collect = false) { + protected function tokenizeDOM($node, &$tokens) + { + $level = 0; + $nodes = array($level => new HTMLPurifier_Queue(array($node))); + $closingNodes = array(); + do { + while (!$nodes[$level]->isEmpty()) { + $node = $nodes[$level]->shift(); // FIFO + $collect = $level > 0 ? true : false; + $needEndingTag = $this->createStartNode($node, $tokens, $collect); + if ($needEndingTag) { + $closingNodes[$level][] = $node; + } + if ($node->childNodes && $node->childNodes->length) { + $level++; + $nodes[$level] = new HTMLPurifier_Queue(); + foreach ($node->childNodes as $childNode) { + $nodes[$level]->push($childNode); + } + } + } + $level--; + if ($level && isset($closingNodes[$level])) { + while ($node = array_pop($closingNodes[$level])) { + $this->createEndNode($node, $tokens); + } + } + } while ($level > 0); + } + /** + * @param DOMNode $node DOMNode to be tokenized. + * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. + * @param bool $collect Says whether or start and close are collected, set to + * false at first recursion because it's the implicit DIV + * tag you're dealing with. + * @return bool if the token needs an endtoken + * @todo data and tagName properties don't seem to exist in DOMNode? + */ + protected function createStartNode($node, &$tokens, $collect) + { // intercept non element nodes. WE MUST catch all of them, // but we're not getting the character reference nodes because // those should have been preprocessed if ($node->nodeType === XML_TEXT_NODE) { $tokens[] = $this->factory->createText($node->data); - return; + return false; } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) { // undo libxml's special treatment of <script> and <style> tags $last = end($tokens); @@ -106,59 +152,61 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer } } $tokens[] = $this->factory->createText($this->parseData($data)); - return; + return false; } elseif ($node->nodeType === XML_COMMENT_NODE) { // this is code is only invoked for comments in script/style in versions // of libxml pre-2.6.28 (regular comments, of course, are still // handled regularly) $tokens[] = $this->factory->createComment($node->data); - return; - } elseif ( + return false; + } elseif ($node->nodeType !== XML_ELEMENT_NODE) { // not-well tested: there may be other nodes we have to grab - $node->nodeType !== XML_ELEMENT_NODE - ) { - return; + return false; } - $attr = $node->hasAttributes() ? - $this->transformAttrToAssoc($node->attributes) : - array(); + $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array(); // We still have to make sure that the element actually IS empty if (!$node->childNodes->length) { if ($collect) { $tokens[] = $this->factory->createEmpty($node->tagName, $attr); } + return false; } else { - if ($collect) { // don't wrap on first iteration + if ($collect) { $tokens[] = $this->factory->createStart( $tag_name = $node->tagName, // somehow, it get's dropped $attr ); } - foreach ($node->childNodes as $node) { - // remember, it's an accumulator. Otherwise, we'd have - // to use array_merge - $this->tokenizeDOM($node, $tokens, true); - } - if ($collect) { - $tokens[] = $this->factory->createEnd($tag_name); - } + return true; } - } + /** + * @param DOMNode $node + * @param HTMLPurifier_Token[] $tokens + */ + protected function createEndNode($node, &$tokens) + { + $tokens[] = $this->factory->createEnd($node->tagName); + } + + /** * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array. * - * @param $attribute_list DOMNamedNodeMap of DOMAttr objects. - * @returns Associative array of attributes. + * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects. + * @return array Associative array of attributes. */ - protected function transformAttrToAssoc($node_map) { + protected function transformAttrToAssoc($node_map) + { // NamedNodeMap is documented very well, so we're using undocumented // features, namely, the fact that it implements Iterator and // has a ->length attribute - if ($node_map->length === 0) return array(); + if ($node_map->length === 0) { + return array(); + } $array = array(); foreach ($node_map as $attr) { $array[$attr->name] = $attr->value; @@ -168,46 +216,64 @@ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer /** * An error handler that mutes all errors + * @param int $errno + * @param string $errstr */ - public function muteErrorHandler($errno, $errstr) {} + public function muteErrorHandler($errno, $errstr) + { + } /** * Callback function for undoing escaping of stray angled brackets * in comments + * @param array $matches + * @return string */ - public function callbackUndoCommentSubst($matches) { - return '<!--' . strtr($matches[1], array('&'=>'&','<'=>'<')) . $matches[2]; + public function callbackUndoCommentSubst($matches) + { + return '<!--' . strtr($matches[1], array('&' => '&', '<' => '<')) . $matches[2]; } /** * Callback function that entity-izes ampersands in comments so that * callbackUndoCommentSubst doesn't clobber them + * @param array $matches + * @return string */ - public function callbackArmorCommentEntities($matches) { + public function callbackArmorCommentEntities($matches) + { return '<!--' . str_replace('&', '&', $matches[1]) . $matches[2]; } /** * Wraps an HTML fragment in the necessary HTML + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string */ - protected function wrapHTML($html, $config, $context) { + protected function wrapHTML($html, $config, $context) + { $def = $config->getDefinition('HTML'); $ret = ''; if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) { $ret .= '<!DOCTYPE html '; - if (!empty($def->doctype->dtdPublic)) $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" '; - if (!empty($def->doctype->dtdSystem)) $ret .= '"' . $def->doctype->dtdSystem . '" '; + if (!empty($def->doctype->dtdPublic)) { + $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" '; + } + if (!empty($def->doctype->dtdSystem)) { + $ret .= '"' . $def->doctype->dtdSystem . '" '; + } $ret .= '>'; } $ret .= '<html><head>'; $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />'; // No protection if $html contains a stray </div>! - $ret .= '</head><body><div>'.$html.'</div></body></html>'; + $ret .= '</head><body>' . $html . '</body></html>'; return $ret; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Lexer/DirectLex.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php similarity index 76% rename from library/HTMLPurifier/Lexer/DirectLex.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php index 456e6e1908..746b6e315f 100644 --- a/library/HTMLPurifier/Lexer/DirectLex.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DirectLex.php @@ -12,30 +12,44 @@ */ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer { - + /** + * @type bool + */ public $tracksLineNumbers = true; /** * Whitespace characters for str(c)spn. + * @type string */ protected $_whitespace = "\x20\x09\x0D\x0A"; /** * Callback function for script CDATA fudge - * @param $matches, in form of array(opening tag, contents, closing tag) + * @param array $matches, in form of array(opening tag, contents, closing tag) + * @return string */ - protected function scriptCallback($matches) { + protected function scriptCallback($matches) + { return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3]; } - public function tokenizeHTML($html, $config, $context) { - + /** + * @param String $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array|HTMLPurifier_Token[] + */ + public function tokenizeHTML($html, $config, $context) + { // special normalization for script tags without any armor // our "armor" heurstic is a < sign any number of whitespaces after // the first script tag if ($config->get('HTML.Trusted')) { - $html = preg_replace_callback('#(<script[^>]*>)(\s*[^<].+?)(</script>)#si', - array($this, 'scriptCallback'), $html); + $html = preg_replace_callback( + '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si', + array($this, 'scriptCallback'), + $html + ); } $html = $this->normalize($html, $config, $context); @@ -55,15 +69,15 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer if ($maintain_line_numbers) { $current_line = 1; - $current_col = 0; + $current_col = 0; $length = strlen($html); } else { $current_line = false; - $current_col = false; + $current_col = false; $length = false; } $context->register('CurrentLine', $current_line); - $context->register('CurrentCol', $current_col); + $context->register('CurrentCol', $current_col); $nl = "\n"; // how often to manually recalculate. This will ALWAYS be right, // but it's pretty wasteful. Set to 0 to turn off @@ -77,16 +91,14 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer // for testing synchronization $loops = 0; - while(++$loops) { - + while (++$loops) { // $cursor is either at the start of a token, or inside of // a tag (i.e. there was a < immediately before it), as indicated // by $inside_tag if ($maintain_line_numbers) { - // $rcursor, however, is always at the start of a token. - $rcursor = $cursor - (int) $inside_tag; + $rcursor = $cursor - (int)$inside_tag; // Column number is cheap, so we calculate it every round. // We're interested at the *end* of the newline string, so @@ -96,14 +108,11 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1); // recalculate lines - if ( - $synchronize_interval && // synchronization is on - $cursor > 0 && // cursor is further than zero - $loops % $synchronize_interval === 0 // time to synchronize! - ) { + if ($synchronize_interval && // synchronization is on + $cursor > 0 && // cursor is further than zero + $loops % $synchronize_interval === 0) { // time to synchronize! $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); } - } $position_next_lt = strpos($html, '<', $cursor); @@ -119,35 +128,42 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer if (!$inside_tag && $position_next_lt !== false) { // We are not inside tag and there still is another tag to parse $token = new - HTMLPurifier_Token_Text( - $this->parseData( - substr( - $html, $cursor, $position_next_lt - $cursor - ) + HTMLPurifier_Token_Text( + $this->parseData( + substr( + $html, + $cursor, + $position_next_lt - $cursor ) - ); + ) + ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor); } $array[] = $token; - $cursor = $position_next_lt + 1; + $cursor = $position_next_lt + 1; $inside_tag = true; continue; } elseif (!$inside_tag) { // We are not inside tag but there are no more tags // If we're already at the end, break - if ($cursor === strlen($html)) break; + if ($cursor === strlen($html)) { + break; + } // Create Text of rest of string $token = new - HTMLPurifier_Token_Text( - $this->parseData( - substr( - $html, $cursor - ) + HTMLPurifier_Token_Text( + $this->parseData( + substr( + $html, + $cursor ) - ); - if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col); + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } $array[] = $token; break; } elseif ($inside_tag && $position_next_gt !== false) { @@ -171,16 +187,16 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer } // Check if it's a comment - if ( - substr($segment, 0, 3) === '!--' - ) { + if (substr($segment, 0, 3) === '!--') { // re-determine segment length, looking for --> $position_comment_end = strpos($html, '-->', $cursor); if ($position_comment_end === false) { // uh oh, we have a comment that extends to // infinity. Can't be helped: set comment // end position to end of string - if ($e) $e->send(E_WARNING, 'Lexer: Unclosed comment'); + if ($e) { + $e->send(E_WARNING, 'Lexer: Unclosed comment'); + } $position_comment_end = strlen($html); $end = true; } else { @@ -189,11 +205,13 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer $strlen_segment = $position_comment_end - $cursor; $segment = substr($html, $cursor, $strlen_segment); $token = new - HTMLPurifier_Token_Comment( - substr( - $segment, 3, $strlen_segment - 3 - ) - ); + HTMLPurifier_Token_Comment( + substr( + $segment, + 3, + $strlen_segment - 3 + ) + ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment); @@ -205,7 +223,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer } // Check if it's an end tag - $is_end_tag = (strpos($segment,'/') === 0); + $is_end_tag = (strpos($segment, '/') === 0); if ($is_end_tag) { $type = substr($segment, 1); $token = new HTMLPurifier_Token_End($type); @@ -224,7 +242,9 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer // text and go our merry way if (!ctype_alpha($segment[0])) { // XML: $segment[0] !== '_' && $segment[0] !== ':' - if ($e) $e->send(E_NOTICE, 'Lexer: Unescaped lt'); + if ($e) { + $e->send(E_NOTICE, 'Lexer: Unescaped lt'); + } $token = new HTMLPurifier_Token_Text('<'); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); @@ -239,7 +259,7 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer // trailing slash. Remember, we could have a tag like <br>, so // any later token processing scripts must convert improperly // classified EmptyTags from StartTags. - $is_self_closing = (strrpos($segment,'/') === $strlen_segment-1); + $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1); if ($is_self_closing) { $strlen_segment--; $segment = substr($segment, 0, $strlen_segment); @@ -269,14 +289,16 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer $attribute_string = trim( substr( - $segment, $position_first_space + $segment, + $position_first_space ) ); if ($attribute_string) { $attr = $this->parseAttributeString( - $attribute_string - , $config, $context - ); + $attribute_string, + $config, + $context + ); } else { $attr = array(); } @@ -296,15 +318,19 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer continue; } else { // inside tag, but there's no ending > sign - if ($e) $e->send(E_WARNING, 'Lexer: Missing gt'); + if ($e) { + $e->send(E_WARNING, 'Lexer: Missing gt'); + } $token = new - HTMLPurifier_Token_Text( - '<' . - $this->parseData( - substr($html, $cursor) - ) - ); - if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col); + HTMLPurifier_Token_Text( + '<' . + $this->parseData( + substr($html, $cursor) + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } // no cursor scroll? Hmm... $array[] = $token; break; @@ -319,8 +345,14 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer /** * PHP 5.0.x compatible substr_count that implements offset and length + * @param string $haystack + * @param string $needle + * @param int $offset + * @param int $length + * @return int */ - protected function substrCount($haystack, $needle, $offset, $length) { + protected function substrCount($haystack, $needle, $offset, $length) + { static $oldVersion; if ($oldVersion === null) { $oldVersion = version_compare(PHP_VERSION, '5.1', '<'); @@ -336,13 +368,18 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer /** * Takes the inside of an HTML tag and makes an assoc array of attributes. * - * @param $string Inside of tag excluding name. - * @returns Assoc array of attributes. + * @param string $string Inside of tag excluding name. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array Assoc array of attributes. */ - public function parseAttributeString($string, $config, $context) { - $string = (string) $string; // quick typecast + public function parseAttributeString($string, $config, $context) + { + $string = (string)$string; // quick typecast - if ($string == '') return array(); // no attributes + if ($string == '') { + return array(); + } // no attributes $e = false; if ($config->get('Core.CollectErrors')) { @@ -361,46 +398,55 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer list($key, $quoted_value) = explode('=', $string); $quoted_value = trim($quoted_value); if (!$key) { - if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key'); + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } return array(); } - if (!$quoted_value) return array($key => ''); + if (!$quoted_value) { + return array($key => ''); + } $first_char = @$quoted_value[0]; - $last_char = @$quoted_value[strlen($quoted_value)-1]; + $last_char = @$quoted_value[strlen($quoted_value) - 1]; $same_quote = ($first_char == $last_char); $open_quote = ($first_char == '"' || $first_char == "'"); - if ( $same_quote && $open_quote) { + if ($same_quote && $open_quote) { // well behaved $value = substr($quoted_value, 1, strlen($quoted_value) - 2); } else { // not well behaved if ($open_quote) { - if ($e) $e->send(E_ERROR, 'Lexer: Missing end quote'); + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing end quote'); + } $value = substr($quoted_value, 1); } else { $value = $quoted_value; } } - if ($value === false) $value = ''; + if ($value === false) { + $value = ''; + } return array($key => $this->parseData($value)); } // setup loop environment - $array = array(); // return assoc array of attributes + $array = array(); // return assoc array of attributes $cursor = 0; // current position in string (moves forward) - $size = strlen($string); // size of the string (stays the same) + $size = strlen($string); // size of the string (stays the same) // if we have unquoted attributes, the parser expects a terminating // space, so let's guarantee that there's always a terminating space. $string .= ' '; - while(true) { - - if ($cursor >= $size) { - break; + $old_cursor = -1; + while ($cursor < $size) { + if ($old_cursor >= $cursor) { + throw new Exception("Infinite loop detected"); } + $old_cursor = $cursor; $cursor += ($value = strspn($string, $this->_whitespace, $cursor)); // grab the key @@ -415,8 +461,10 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer $key = substr($string, $key_begin, $key_end - $key_begin); if (!$key) { - if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key'); - $cursor += strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop continue; // empty key } @@ -467,24 +515,25 @@ class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer } $value = substr($string, $value_begin, $value_end - $value_begin); - if ($value === false) $value = ''; + if ($value === false) { + $value = ''; + } $array[$key] = $this->parseData($value); $cursor++; - } else { // boolattr if ($key !== '') { $array[$key] = $key; } else { // purely theoretical - if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key'); + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } } - } } return $array; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php new file mode 100644 index 0000000000..ff4fa218fb --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php @@ -0,0 +1,4787 @@ +<?php + +/** + * Experimental HTML5-based parser using Jeroen van der Meer's PH5P library. + * Occupies space in the HTML5 pseudo-namespace, which may cause conflicts. + * + * @note + * Recent changes to PHP's DOM extension have resulted in some fatal + * error conditions with the original version of PH5P. Pending changes, + * this lexer will punt to DirectLex if DOM throws an exception. + */ + +class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex +{ + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function tokenizeHTML($html, $config, $context) + { + $new_html = $this->normalize($html, $config, $context); + $new_html = $this->wrapHTML($new_html, $config, $context); + try { + $parser = new HTML5($new_html); + $doc = $parser->save(); + } catch (DOMException $e) { + // Uh oh, it failed. Punt to DirectLex. + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $context->register('PH5PError', $e); // save the error, so we can detect it + return $lexer->tokenizeHTML($html, $config, $context); // use original HTML + } + $tokens = array(); + $this->tokenizeDOM( + $doc->getElementsByTagName('html')->item(0)-> // <html> + getElementsByTagName('body')->item(0) // <body> + , + $tokens + ); + return $tokens; + } +} + +/* + +Copyright 2007 Jeroen van der Meer <http://jero.net/> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +class HTML5 +{ + private $data; + private $char; + private $EOF; + private $state; + private $tree; + private $token; + private $content_model; + private $escape = false; + private $entities = array( + 'AElig;', + 'AElig', + 'AMP;', + 'AMP', + 'Aacute;', + 'Aacute', + 'Acirc;', + 'Acirc', + 'Agrave;', + 'Agrave', + 'Alpha;', + 'Aring;', + 'Aring', + 'Atilde;', + 'Atilde', + 'Auml;', + 'Auml', + 'Beta;', + 'COPY;', + 'COPY', + 'Ccedil;', + 'Ccedil', + 'Chi;', + 'Dagger;', + 'Delta;', + 'ETH;', + 'ETH', + 'Eacute;', + 'Eacute', + 'Ecirc;', + 'Ecirc', + 'Egrave;', + 'Egrave', + 'Epsilon;', + 'Eta;', + 'Euml;', + 'Euml', + 'GT;', + 'GT', + 'Gamma;', + 'Iacute;', + 'Iacute', + 'Icirc;', + 'Icirc', + 'Igrave;', + 'Igrave', + 'Iota;', + 'Iuml;', + 'Iuml', + 'Kappa;', + 'LT;', + 'LT', + 'Lambda;', + 'Mu;', + 'Ntilde;', + 'Ntilde', + 'Nu;', + 'OElig;', + 'Oacute;', + 'Oacute', + 'Ocirc;', + 'Ocirc', + 'Ograve;', + 'Ograve', + 'Omega;', + 'Omicron;', + 'Oslash;', + 'Oslash', + 'Otilde;', + 'Otilde', + 'Ouml;', + 'Ouml', + 'Phi;', + 'Pi;', + 'Prime;', + 'Psi;', + 'QUOT;', + 'QUOT', + 'REG;', + 'REG', + 'Rho;', + 'Scaron;', + 'Sigma;', + 'THORN;', + 'THORN', + 'TRADE;', + 'Tau;', + 'Theta;', + 'Uacute;', + 'Uacute', + 'Ucirc;', + 'Ucirc', + 'Ugrave;', + 'Ugrave', + 'Upsilon;', + 'Uuml;', + 'Uuml', + 'Xi;', + 'Yacute;', + 'Yacute', + 'Yuml;', + 'Zeta;', + 'aacute;', + 'aacute', + 'acirc;', + 'acirc', + 'acute;', + 'acute', + 'aelig;', + 'aelig', + 'agrave;', + 'agrave', + 'alefsym;', + 'alpha;', + 'amp;', + 'amp', + 'and;', + 'ang;', + 'apos;', + 'aring;', + 'aring', + 'asymp;', + 'atilde;', + 'atilde', + 'auml;', + 'auml', + 'bdquo;', + 'beta;', + 'brvbar;', + 'brvbar', + 'bull;', + 'cap;', + 'ccedil;', + 'ccedil', + 'cedil;', + 'cedil', + 'cent;', + 'cent', + 'chi;', + 'circ;', + 'clubs;', + 'cong;', + 'copy;', + 'copy', + 'crarr;', + 'cup;', + 'curren;', + 'curren', + 'dArr;', + 'dagger;', + 'darr;', + 'deg;', + 'deg', + 'delta;', + 'diams;', + 'divide;', + 'divide', + 'eacute;', + 'eacute', + 'ecirc;', + 'ecirc', + 'egrave;', + 'egrave', + 'empty;', + 'emsp;', + 'ensp;', + 'epsilon;', + 'equiv;', + 'eta;', + 'eth;', + 'eth', + 'euml;', + 'euml', + 'euro;', + 'exist;', + 'fnof;', + 'forall;', + 'frac12;', + 'frac12', + 'frac14;', + 'frac14', + 'frac34;', + 'frac34', + 'frasl;', + 'gamma;', + 'ge;', + 'gt;', + 'gt', + 'hArr;', + 'harr;', + 'hearts;', + 'hellip;', + 'iacute;', + 'iacute', + 'icirc;', + 'icirc', + 'iexcl;', + 'iexcl', + 'igrave;', + 'igrave', + 'image;', + 'infin;', + 'int;', + 'iota;', + 'iquest;', + 'iquest', + 'isin;', + 'iuml;', + 'iuml', + 'kappa;', + 'lArr;', + 'lambda;', + 'lang;', + 'laquo;', + 'laquo', + 'larr;', + 'lceil;', + 'ldquo;', + 'le;', + 'lfloor;', + 'lowast;', + 'loz;', + 'lrm;', + 'lsaquo;', + 'lsquo;', + 'lt;', + 'lt', + 'macr;', + 'macr', + 'mdash;', + 'micro;', + 'micro', + 'middot;', + 'middot', + 'minus;', + 'mu;', + 'nabla;', + 'nbsp;', + 'nbsp', + 'ndash;', + 'ne;', + 'ni;', + 'not;', + 'not', + 'notin;', + 'nsub;', + 'ntilde;', + 'ntilde', + 'nu;', + 'oacute;', + 'oacute', + 'ocirc;', + 'ocirc', + 'oelig;', + 'ograve;', + 'ograve', + 'oline;', + 'omega;', + 'omicron;', + 'oplus;', + 'or;', + 'ordf;', + 'ordf', + 'ordm;', + 'ordm', + 'oslash;', + 'oslash', + 'otilde;', + 'otilde', + 'otimes;', + 'ouml;', + 'ouml', + 'para;', + 'para', + 'part;', + 'permil;', + 'perp;', + 'phi;', + 'pi;', + 'piv;', + 'plusmn;', + 'plusmn', + 'pound;', + 'pound', + 'prime;', + 'prod;', + 'prop;', + 'psi;', + 'quot;', + 'quot', + 'rArr;', + 'radic;', + 'rang;', + 'raquo;', + 'raquo', + 'rarr;', + 'rceil;', + 'rdquo;', + 'real;', + 'reg;', + 'reg', + 'rfloor;', + 'rho;', + 'rlm;', + 'rsaquo;', + 'rsquo;', + 'sbquo;', + 'scaron;', + 'sdot;', + 'sect;', + 'sect', + 'shy;', + 'shy', + 'sigma;', + 'sigmaf;', + 'sim;', + 'spades;', + 'sub;', + 'sube;', + 'sum;', + 'sup1;', + 'sup1', + 'sup2;', + 'sup2', + 'sup3;', + 'sup3', + 'sup;', + 'supe;', + 'szlig;', + 'szlig', + 'tau;', + 'there4;', + 'theta;', + 'thetasym;', + 'thinsp;', + 'thorn;', + 'thorn', + 'tilde;', + 'times;', + 'times', + 'trade;', + 'uArr;', + 'uacute;', + 'uacute', + 'uarr;', + 'ucirc;', + 'ucirc', + 'ugrave;', + 'ugrave', + 'uml;', + 'uml', + 'upsih;', + 'upsilon;', + 'uuml;', + 'uuml', + 'weierp;', + 'xi;', + 'yacute;', + 'yacute', + 'yen;', + 'yen', + 'yuml;', + 'yuml', + 'zeta;', + 'zwj;', + 'zwnj;' + ); + + const PCDATA = 0; + const RCDATA = 1; + const CDATA = 2; + const PLAINTEXT = 3; + + const DOCTYPE = 0; + const STARTTAG = 1; + const ENDTAG = 2; + const COMMENT = 3; + const CHARACTR = 4; + const EOF = 5; + + public function __construct($data) + { + $this->data = $data; + $this->char = -1; + $this->EOF = strlen($data); + $this->tree = new HTML5TreeConstructer; + $this->content_model = self::PCDATA; + + $this->state = 'data'; + + while ($this->state !== null) { + $this->{$this->state . 'State'}(); + } + } + + public function save() + { + return $this->tree->save(); + } + + private function char() + { + return ($this->char < $this->EOF) + ? $this->data[$this->char] + : false; + } + + private function character($s, $l = 0) + { + if ($s + $l < $this->EOF) { + if ($l === 0) { + return $this->data[$s]; + } else { + return substr($this->data, $s, $l); + } + } + } + + private function characters($char_class, $start) + { + return preg_replace('#^([' . $char_class . ']+).*#s', '\\1', substr($this->data, $start)); + } + + private function dataState() + { + // Consume the next input character + $this->char++; + $char = $this->char(); + + if ($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { + /* U+0026 AMPERSAND (&) + When the content model flag is set to one of the PCDATA or RCDATA + states: switch to the entity data state. Otherwise: treat it as per + the "anything else" entry below. */ + $this->state = 'entityData'; + + } elseif ($char === '-') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is false, and there are at + least three characters before this one in the input stream, and the + last four characters in the input stream, including this one, are + U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, + and U+002D HYPHEN-MINUS ("<!--"), then set the escape flag to true. */ + if (($this->content_model === self::RCDATA || $this->content_model === + self::CDATA) && $this->escape === false && + $this->char >= 3 && $this->character($this->char - 4, 4) === '<!--' + ) { + $this->escape = true; + } + + /* In any case, emit the input character as a character token. Stay + in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + /* U+003C LESS-THAN SIGN (<) */ + } elseif ($char === '<' && ($this->content_model === self::PCDATA || + (($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === false)) + ) { + /* When the content model flag is set to the PCDATA state: switch + to the tag open state. + + When the content model flag is set to either the RCDATA state or + the CDATA state and the escape flag is false: switch to the tag + open state. + + Otherwise: treat it as per the "anything else" entry below. */ + $this->state = 'tagOpen'; + + /* U+003E GREATER-THAN SIGN (>) */ + } elseif ($char === '>') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is true, and the last three + characters in the input stream including this one are U+002D + HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"), + set the escape flag to false. */ + if (($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === true && + $this->character($this->char, 3) === '-->' + ) { + $this->escape = false; + } + + /* In any case, emit the input character as a character token. + Stay in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + } elseif ($this->char === $this->EOF) { + /* EOF + Emit an end-of-file token. */ + $this->EOF(); + + } elseif ($this->content_model === self::PLAINTEXT) { + /* When the content model flag is set to the PLAINTEXT state + THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of + the text and emit it as a character token. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => substr($this->data, $this->char) + ) + ); + + $this->EOF(); + + } else { + /* Anything else + THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that + otherwise would also be treated as a character token and emit it + as a single character token. Stay in the data state. */ + $len = strcspn($this->data, '<&', $this->char); + $char = substr($this->data, $this->char, $len); + $this->char += $len - 1; + + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + $this->state = 'data'; + } + } + + private function entityDataState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + // Finally, switch to the data state. + $this->state = 'data'; + } + + private function tagOpenState() + { + switch ($this->content_model) { + case self::RCDATA: + case self::CDATA: + /* If the next input character is a U+002F SOLIDUS (/) character, + consume it and switch to the close tag open state. If the next + input character is not a U+002F SOLIDUS (/) character, emit a + U+003C LESS-THAN SIGN character token and switch to the data + state to process the next input character. */ + if ($this->character($this->char + 1) === '/') { + $this->char++; + $this->state = 'closeTagOpen'; + + } else { + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->state = 'data'; + } + break; + + case self::PCDATA: + // If the content model flag is set to the PCDATA state + // Consume the next input character: + $this->char++; + $char = $this->char(); + + if ($char === '!') { + /* U+0021 EXCLAMATION MARK (!) + Switch to the markup declaration open state. */ + $this->state = 'markupDeclarationOpen'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Switch to the close tag open state. */ + $this->state = 'closeTagOpen'; + + } elseif (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new start tag token, set its tag name to the lowercase + version of the input character (add 0x0020 to the character's code + point), then switch to the tag name state. (Don't emit the token + yet; further details will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::STARTTAG, + 'attr' => array() + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Emit a U+003C LESS-THAN SIGN character token and a + U+003E GREATER-THAN SIGN character token. Switch to the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<>' + ) + ); + + $this->state = 'data'; + + } elseif ($char === '?') { + /* U+003F QUESTION MARK (?) + Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + + } else { + /* Anything else + Parse error. Emit a U+003C LESS-THAN SIGN character token and + reconsume the current input character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->char--; + $this->state = 'data'; + } + break; + } + } + + private function closeTagOpenState() + { + $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); + $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; + + if (($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && + (!$the_same || ($the_same && (!preg_match( + '/[\t\n\x0b\x0c >\/]/', + $this->character($this->char + 1 + strlen($next_node)) + ) || $this->EOF === $this->char))) + ) { + /* If the content model flag is set to the RCDATA or CDATA states then + examine the next few characters. If they do not match the tag name of + the last start tag token emitted (case insensitively), or if they do but + they are not immediately followed by one of the following characters: + * U+0009 CHARACTER TABULATION + * U+000A LINE FEED (LF) + * U+000B LINE TABULATION + * U+000C FORM FEED (FF) + * U+0020 SPACE + * U+003E GREATER-THAN SIGN (>) + * U+002F SOLIDUS (/) + * EOF + ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character + token, a U+002F SOLIDUS character token, and switch to the data state + to process the next input character. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '</' + ) + ); + + $this->state = 'data'; + + } else { + /* Otherwise, if the content model flag is set to the PCDATA state, + or if the next few characters do match that tag name, consume the + next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new end tag token, set its tag name to the lowercase version + of the input character (add 0x0020 to the character's code point), then + switch to the tag name state. (Don't emit the token yet; further details + will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::ENDTAG + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Switch to the data state. */ + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F + SOLIDUS character token. Reconsume the EOF character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '</' + ) + ); + + $this->char--; + $this->state = 'data'; + + } else { + /* Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + } + } + } + + private function tagNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } else { + /* Anything else + Append the current input character to the current tag token's tag name. + Stay in the tag name state. */ + $this->token['name'] .= strtolower($char); + $this->state = 'tagName'; + } + } + + private function beforeAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Stay in the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function attributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's name. + Stay in the attribute name state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['name'] .= strtolower($char); + + $this->state = 'attributeName'; + } + } + + private function afterAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the after attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the + before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function beforeAttributeValueState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the attribute value (double-quoted) state. */ + $this->state = 'attributeValueDoubleQuoted'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the attribute value (unquoted) state and reconsume + this input character. */ + $this->char--; + $this->state = 'attributeValueUnquoted'; + + } elseif ($char === '\'') { + /* U+0027 APOSTROPHE (') + Switch to the attribute value (single-quoted) state. */ + $this->state = 'attributeValueSingleQuoted'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Switch to the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function attributeValueDoubleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('double'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (double-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueDoubleQuoted'; + } + } + + private function attributeValueSingleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '\'') { + /* U+0022 QUOTATION MARK (') + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('single'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (single-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueSingleQuoted'; + } + } + + private function attributeValueUnquotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState(); + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function entityInAttributeValueState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, append a U+0026 AMPERSAND character to the + // current attribute's value. Otherwise, emit the character token that + // was returned. + $char = (!$entity) + ? '&' + : $entity; + + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + } + + private function bogusCommentState() + { + /* Consume every character up to the first U+003E GREATER-THAN SIGN + character (>) or the end of the file (EOF), whichever comes first. Emit + a comment token whose data is the concatenation of all the characters + starting from and including the character that caused the state machine + to switch into the bogus comment state, up to and including the last + consumed character before the U+003E character, if any, or up to the + end of the file otherwise. (If the comment was started by the end of + the file (EOF), the token is empty.) */ + $data = $this->characters('^>', $this->char); + $this->emitToken( + array( + 'data' => $data, + 'type' => self::COMMENT + ) + ); + + $this->char += strlen($data); + + /* Switch to the data state. */ + $this->state = 'data'; + + /* If the end of the file was reached, reconsume the EOF character. */ + if ($this->char === $this->EOF) { + $this->char = $this->EOF - 1; + } + } + + private function markupDeclarationOpenState() + { + /* If the next two characters are both U+002D HYPHEN-MINUS (-) + characters, consume those two characters, create a comment token whose + data is the empty string, and switch to the comment state. */ + if ($this->character($this->char + 1, 2) === '--') { + $this->char += 2; + $this->state = 'comment'; + $this->token = array( + 'data' => null, + 'type' => self::COMMENT + ); + + /* Otherwise if the next seven chacacters are a case-insensitive match + for the word "DOCTYPE", then consume those characters and switch to the + DOCTYPE state. */ + } elseif (strtolower($this->character($this->char + 1, 7)) === 'doctype') { + $this->char += 7; + $this->state = 'doctype'; + + /* Otherwise, is is a parse error. Switch to the bogus comment state. + The next character that is consumed, if any, is the first character + that will be in the comment. */ + } else { + $this->char++; + $this->state = 'bogusComment'; + } + } + + private function commentState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment dash state */ + $this->state = 'commentDash'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append the input character to the comment token's data. Stay in + the comment state. */ + $this->token['data'] .= $char; + } + } + + private function commentDashState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment end state */ + $this->state = 'commentEnd'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append a U+002D HYPHEN-MINUS (-) character and the input + character to the comment token's data. Switch to the comment state. */ + $this->token['data'] .= '-' . $char; + $this->state = 'comment'; + } + } + + private function commentEndState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '-') { + $this->token['data'] .= '-'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['data'] .= '--' . $char; + $this->state = 'comment'; + } + } + + private function doctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'beforeDoctypeName'; + + } else { + $this->char--; + $this->state = 'beforeDoctypeName'; + } + } + + private function beforeDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the before DOCTYPE name state. + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token = array( + 'name' => strtoupper($char), + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + + } elseif ($char === '>') { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->char--; + $this->state = 'data'; + + } else { + $this->token = array( + 'name' => $char, + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + } + } + + private function doctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'AfterDoctypeName'; + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token['name'] .= strtoupper($char); + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['name'] .= $char; + } + + $this->token['error'] = ($this->token['name'] === 'HTML') + ? false + : true; + } + + private function afterDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the DOCTYPE name state. + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['error'] = true; + $this->state = 'bogusDoctype'; + } + } + + private function bogusDoctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + // Stay in the bogus DOCTYPE state. + } + } + + private function entity() + { + $start = $this->char; + + // This section defines how to consume an entity. This definition is + // used when parsing entities in text and in attributes. + + // The behaviour depends on the identity of the next character (the + // one immediately after the U+0026 AMPERSAND character): + + switch ($this->character($this->char + 1)) { + // U+0023 NUMBER SIGN (#) + case '#': + + // The behaviour further depends on the character after the + // U+0023 NUMBER SIGN: + switch ($this->character($this->char + 1)) { + // U+0078 LATIN SMALL LETTER X + // U+0058 LATIN CAPITAL LETTER X + case 'x': + case 'X': + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 + // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER + // A, through to U+0046 LATIN CAPITAL LETTER F (in other + // words, 0-9, A-F, a-f). + $char = 1; + $char_class = '0-9A-Fa-f'; + break; + + // Anything else + default: + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE (i.e. just 0-9). + $char = 0; + $char_class = '0-9'; + break; + } + + // Consume as many characters as match the range of characters + // given above. + $this->char++; + $e_name = $this->characters($char_class, $this->char + $char + 1); + $entity = $this->character($start, $this->char); + $cond = strlen($e_name) > 0; + + // The rest of the parsing happens bellow. + break; + + // Anything else + default: + // Consume the maximum number of characters possible, with the + // consumed characters case-sensitively matching one of the + // identifiers in the first column of the entities table. + $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); + $len = strlen($e_name); + + for ($c = 1; $c <= $len; $c++) { + $id = substr($e_name, 0, $c); + $this->char++; + + if (in_array($id, $this->entities)) { + if ($e_name[$c - 1] !== ';') { + if ($c < $len && $e_name[$c] == ';') { + $this->char++; // consume extra semicolon + } + } + $entity = $id; + break; + } + } + + $cond = isset($entity); + // The rest of the parsing happens bellow. + break; + } + + if (!$cond) { + // If no match can be made, then this is a parse error. No + // characters are consumed, and nothing is returned. + $this->char = $start; + return false; + } + + // Return a character token for the character corresponding to the + // entity name (as given by the second column of the entities table). + return html_entity_decode('&' . $entity . ';', ENT_QUOTES, 'UTF-8'); + } + + private function emitToken($token) + { + $emit = $this->tree->emitToken($token); + + if (is_int($emit)) { + $this->content_model = $emit; + + } elseif ($token['type'] === self::ENDTAG) { + $this->content_model = self::PCDATA; + } + } + + private function EOF() + { + $this->state = null; + $this->tree->emitToken( + array( + 'type' => self::EOF + ) + ); + } +} + +class HTML5TreeConstructer +{ + public $stack = array(); + + private $phase; + private $mode; + private $dom; + private $foster_parent = null; + private $a_formatting = array(); + + private $head_pointer = null; + private $form_pointer = null; + + private $scoping = array('button', 'caption', 'html', 'marquee', 'object', 'table', 'td', 'th'); + private $formatting = array( + 'a', + 'b', + 'big', + 'em', + 'font', + 'i', + 'nobr', + 's', + 'small', + 'strike', + 'strong', + 'tt', + 'u' + ); + private $special = array( + 'address', + 'area', + 'base', + 'basefont', + 'bgsound', + 'blockquote', + 'body', + 'br', + 'center', + 'col', + 'colgroup', + 'dd', + 'dir', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'iframe', + 'image', + 'img', + 'input', + 'isindex', + 'li', + 'link', + 'listing', + 'menu', + 'meta', + 'noembed', + 'noframes', + 'noscript', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'plaintext', + 'pre', + 'script', + 'select', + 'spacer', + 'style', + 'tbody', + 'textarea', + 'tfoot', + 'thead', + 'title', + 'tr', + 'ul', + 'wbr' + ); + + // The different phases. + const INIT_PHASE = 0; + const ROOT_PHASE = 1; + const MAIN_PHASE = 2; + const END_PHASE = 3; + + // The different insertion modes for the main phase. + const BEFOR_HEAD = 0; + const IN_HEAD = 1; + const AFTER_HEAD = 2; + const IN_BODY = 3; + const IN_TABLE = 4; + const IN_CAPTION = 5; + const IN_CGROUP = 6; + const IN_TBODY = 7; + const IN_ROW = 8; + const IN_CELL = 9; + const IN_SELECT = 10; + const AFTER_BODY = 11; + const IN_FRAME = 12; + const AFTR_FRAME = 13; + + // The different types of elements. + const SPECIAL = 0; + const SCOPING = 1; + const FORMATTING = 2; + const PHRASING = 3; + + const MARKER = 0; + + public function __construct() + { + $this->phase = self::INIT_PHASE; + $this->mode = self::BEFOR_HEAD; + $this->dom = new DOMDocument; + + $this->dom->encoding = 'UTF-8'; + $this->dom->preserveWhiteSpace = true; + $this->dom->substituteEntities = true; + $this->dom->strictErrorChecking = false; + } + + // Process tag tokens + public function emitToken($token) + { + switch ($this->phase) { + case self::INIT_PHASE: + return $this->initPhase($token); + break; + case self::ROOT_PHASE: + return $this->rootElementPhase($token); + break; + case self::MAIN_PHASE: + return $this->mainPhase($token); + break; + case self::END_PHASE : + return $this->trailingEndPhase($token); + break; + } + } + + private function initPhase($token) + { + /* Initially, the tree construction stage must handle each token + emitted from the tokenisation stage as follows: */ + + /* A DOCTYPE token that is marked as being in error + A comment token + A start tag token + An end tag token + A character token that is not one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE + An end-of-file token */ + if ((isset($token['error']) && $token['error']) || + $token['type'] === HTML5::COMMENT || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF || + ($token['type'] === HTML5::CHARACTR && isset($token['data']) && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) + ) { + /* This specification does not define how to handle this case. In + particular, user agents may ignore the entirety of this specification + altogether for such documents, and instead invoke special parse modes + with a greater emphasis on backwards compatibility. */ + + $this->phase = self::ROOT_PHASE; + return $this->rootElementPhase($token); + + /* A DOCTYPE token marked as being correct */ + } elseif (isset($token['error']) && !$token['error']) { + /* Append a DocumentType node to the Document node, with the name + attribute set to the name given in the DOCTYPE token (which will be + "HTML"), and the other attributes specific to DocumentType objects + set to null, empty lists, or the empty string as appropriate. */ + $doctype = new DOMDocumentType(null, null, 'HTML'); + + /* Then, switch to the root element phase of the tree construction + stage. */ + $this->phase = self::ROOT_PHASE; + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif (isset($token['data']) && preg_match( + '/^[\t\n\x0b\x0c ]+$/', + $token['data'] + ) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + } + } + + private function rootElementPhase($token) + { + /* After the initial phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED + (FF), or U+0020 SPACE + A start tag token + An end tag token + An end-of-file token */ + } elseif (($token['type'] === HTML5::CHARACTR && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF + ) { + /* Create an HTMLElement node with the tag name html, in the HTML + namespace. Append it to the Document object. Switch to the main + phase and reprocess the current token. */ + $html = $this->dom->createElement('html'); + $this->dom->appendChild($html); + $this->stack[] = $html; + + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + } + } + + private function mainPhase($token) + { + /* Tokens in the main phase must be handled as follows: */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A start tag token with the tag name "html" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { + /* If this start tag token was not the first start tag token, then + it is a parse error. */ + + /* For each attribute on the token, check to see if the attribute + is already present on the top element of the stack of open elements. + If it is not, add the attribute and its corresponding value to that + element. */ + foreach ($token['attr'] as $attr) { + if (!$this->stack[0]->hasAttribute($attr['name'])) { + $this->stack[0]->setAttribute($attr['name'], $attr['value']); + } + } + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Anything else. */ + } else { + /* Depends on the insertion mode: */ + switch ($this->mode) { + case self::BEFOR_HEAD: + return $this->beforeHead($token); + break; + case self::IN_HEAD: + return $this->inHead($token); + break; + case self::AFTER_HEAD: + return $this->afterHead($token); + break; + case self::IN_BODY: + return $this->inBody($token); + break; + case self::IN_TABLE: + return $this->inTable($token); + break; + case self::IN_CAPTION: + return $this->inCaption($token); + break; + case self::IN_CGROUP: + return $this->inColumnGroup($token); + break; + case self::IN_TBODY: + return $this->inTableBody($token); + break; + case self::IN_ROW: + return $this->inRow($token); + break; + case self::IN_CELL: + return $this->inCell($token); + break; + case self::IN_SELECT: + return $this->inSelect($token); + break; + case self::AFTER_BODY: + return $this->afterBody($token); + break; + case self::IN_FRAME: + return $this->inFrameset($token); + break; + case self::AFTR_FRAME: + return $this->afterFrameset($token); + break; + case self::END_PHASE: + return $this->trailingEndPhase($token); + break; + } + } + } + + private function beforeHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "head" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { + /* Create an element for the token, append the new element to the + current node and push it onto the stack of open elements. */ + $element = $this->insertElement($token); + + /* Set the head element pointer to this new element node. */ + $this->head_pointer = $element; + + /* Change the insertion mode to "in head". */ + $this->mode = self::IN_HEAD; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title". Or an end tag with the tag name "html". + Or a character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or any other start tag token */ + } elseif ($token['type'] === HTML5::STARTTAG || + ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || + ($token['type'] === HTML5::CHARACTR && !preg_match( + '/^[\t\n\x0b\x0c ]$/', + $token['data'] + )) + ) { + /* Act as if a start tag token with the tag name "head" and no + attributes had been seen, then reprocess the current token. */ + $this->beforeHead( + array( + 'name' => 'head', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inHead($token); + + /* Any other end tag */ + } elseif ($token['type'] === HTML5::ENDTAG) { + /* Parse error. Ignore the token. */ + } + } + + private function inHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. + + THIS DIFFERS FROM THE SPEC: If the current node is either a title, style + or script element, append the character to the current node regardless + of its content. */ + if (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( + $token['type'] === HTML5::CHARACTR && in_array( + end($this->stack)->nodeName, + array('title', 'style', 'script') + )) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('title', 'style', 'script')) + ) { + array_pop($this->stack); + return HTML5::PCDATA; + + /* A start tag with the tag name "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $element = $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the RCDATA state. */ + return HTML5::RCDATA; + + /* A start tag with the tag name "style" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "script" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { + /* Create an element for the token. */ + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "base", "link", or "meta" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta') + ) + ) { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + array_pop($this->stack); + + } else { + $this->insertElement($token); + } + + /* An end tag with the tag name "head" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { + /* If the current node is a head element, pop the current node off + the stack of open elements. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + array_pop($this->stack); + + /* Otherwise, this is a parse error. */ + } else { + // k + } + + /* Change the insertion mode to "after head". */ + $this->mode = self::AFTER_HEAD; + + /* A start tag with the tag name "head" or an end tag except "html". */ + } elseif (($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || + ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html') + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* If the current node is a head element, act as if an end tag + token with the tag name "head" had been seen. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + $this->inHead( + array( + 'name' => 'head', + 'type' => HTML5::ENDTAG + ) + ); + + /* Otherwise, change the insertion mode to "after head". */ + } else { + $this->mode = self::AFTER_HEAD; + } + + /* Then, reprocess the current token. */ + return $this->afterHead($token); + } + } + + private function afterHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "body" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { + /* Insert a body element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in body". */ + $this->mode = self::IN_BODY; + + /* A start tag token with the tag name "frameset" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { + /* Insert a frameset element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in frameset". */ + $this->mode = self::IN_FRAME; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta', 'script', 'style', 'title') + ) + ) { + /* Parse error. Switch the insertion mode back to "in head" and + reprocess the token. */ + $this->mode = self::IN_HEAD; + return $this->inHead($token); + + /* Anything else */ + } else { + /* Act as if a start tag token with the tag name "body" and no + attributes had been seen, and then reprocess the current token. */ + $this->afterHead( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inBody($token); + } + } + + private function inBody($token) + { + /* Handle the token as follows: */ + + switch ($token['type']) { + /* A character token */ + case HTML5::CHARACTR: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + break; + + /* A comment token */ + case HTML5::COMMENT: + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + break; + + case HTML5::STARTTAG: + switch ($token['name']) { + /* A start tag token whose tag name is one of: "script", + "style" */ + case 'script': + case 'style': + /* Process the token as if the insertion mode had been "in + head". */ + return $this->inHead($token); + break; + + /* A start tag token whose tag name is one of: "base", "link", + "meta", "title" */ + case 'base': + case 'link': + case 'meta': + case 'title': + /* Parse error. Process the token as if the insertion mode + had been "in head". */ + return $this->inHead($token); + break; + + /* A start tag token with the tag name "body" */ + case 'body': + /* Parse error. If the second element on the stack of open + elements is not a body element, or, if the stack of open + elements has only one node on it, then ignore the token. + (innerHTML case) */ + if (count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { + // Ignore + + /* Otherwise, for each attribute on the token, check to see + if the attribute is already present on the body element (the + second element) on the stack of open elements. If it is not, + add the attribute and its corresponding value to that + element. */ + } else { + foreach ($token['attr'] as $attr) { + if (!$this->stack[1]->hasAttribute($attr['name'])) { + $this->stack[1]->setAttribute($attr['name'], $attr['value']); + } + } + } + break; + + /* A start tag whose tag name is one of: "address", + "blockquote", "center", "dir", "div", "dl", "fieldset", + "listing", "menu", "ol", "p", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'p': + case 'ul': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "form" */ + case 'form': + /* If the form element pointer is not null, ignore the + token with a parse error. */ + if ($this->form_pointer !== null) { + // Ignore. + + /* Otherwise: */ + } else { + /* If the stack of open elements has a p element in + scope, then act as if an end tag with the tag name p + had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token, and set the + form element pointer to point to the element created. */ + $element = $this->insertElement($token); + $this->form_pointer = $element; + } + break; + + /* A start tag whose tag name is "li", "dd" or "dt" */ + case 'li': + case 'dd': + case 'dt': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + $stack_length = count($this->stack) - 1; + + for ($n = $stack_length; 0 <= $n; $n--) { + /* 1. Initialise node to be the current node (the + bottommost node of the stack). */ + $stop = false; + $node = $this->stack[$n]; + $cat = $this->getElementCategory($node->tagName); + + /* 2. If node is an li, dd or dt element, then pop all + the nodes from the current node up to node, including + node, then stop this algorithm. */ + if ($token['name'] === $node->tagName || ($token['name'] !== 'li' + && ($node->tagName === 'dd' || $node->tagName === 'dt')) + ) { + for ($x = $stack_length; $x >= $n; $x--) { + array_pop($this->stack); + } + + break; + } + + /* 3. If node is not in the formatting category, and is + not in the phrasing category, and is not an address or + div element, then stop this algorithm. */ + if ($cat !== self::FORMATTING && $cat !== self::PHRASING && + $node->tagName !== 'address' && $node->tagName !== 'div' + ) { + break; + } + } + + /* Finally, insert an HTML element with the same tag + name as the token's. */ + $this->insertElement($token); + break; + + /* A start tag token whose tag name is "plaintext" */ + case 'plaintext': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + return HTML5::PLAINTEXT; + break; + + /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + this is a parse error; pop elements from the stack until an + element with one of those tag names has been popped from the + stack. */ + while ($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { + array_pop($this->stack); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "a" */ + case 'a': + /* If the list of active formatting elements contains + an element whose tag name is "a" between the end of the + list and the last marker on the list (or the start of + the list if there is no marker on the list), then this + is a parse error; act as if an end tag with the tag name + "a" had been seen, then remove that element from the list + of active formatting elements and the stack of open + elements if the end tag didn't already remove it (it + might not have if the element is not in table scope). */ + $leng = count($this->a_formatting); + + for ($n = $leng - 1; $n >= 0; $n--) { + if ($this->a_formatting[$n] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$n]->nodeName === 'a') { + $this->emitToken( + array( + 'name' => 'a', + 'type' => HTML5::ENDTAG + ) + ); + break; + } + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag whose tag name is one of: "b", "big", "em", "font", + "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag token whose tag name is "button" */ + case 'button': + /* If the stack of open elements has a button element in scope, + then this is a parse error; act as if an end tag with the tag + name "button" had been seen, then reprocess the token. (We don't + do that. Unnecessary.) */ + if ($this->elementInScope('button')) { + $this->inBody( + array( + 'name' => 'button', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is one of: "marquee", "object" */ + case 'marquee': + case 'object': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is "xmp" */ + case 'xmp': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Switch the content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "table" */ + case 'table': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + break; + + /* A start tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'img': + case 'param': + case 'spacer': + case 'wbr': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "hr" */ + case 'hr': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "image" */ + case 'image': + /* Parse error. Change the token's tag name to "img" and + reprocess it. (Don't ask.) */ + $token['name'] = 'img'; + return $this->inBody($token); + break; + + /* A start tag whose tag name is "input" */ + case 'input': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an input element for the token. */ + $element = $this->insertElement($token, false); + + /* If the form element pointer is not null, then associate the + input element with the form element pointed to by the form + element pointer. */ + $this->form_pointer !== null + ? $this->form_pointer->appendChild($element) + : end($this->stack)->appendChild($element); + + /* Pop that input element off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "isindex" */ + case 'isindex': + /* Parse error. */ + // w/e + + /* If the form element pointer is not null, + then ignore the token. */ + if ($this->form_pointer === null) { + /* Act as if a start tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a stream of character tokens had been seen. */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if a start tag token with the tag name "input" + had been seen, with all the attributes from the "isindex" + token, except with the "name" attribute set to the value + "isindex" (ignoring any explicit "name" attribute). */ + $attr = $token['attr']; + $attr[] = array('name' => 'name', 'value' => 'isindex'); + + $this->inBody( + array( + 'name' => 'input', + 'type' => HTML5::STARTTAG, + 'attr' => $attr + ) + ); + + /* Act as if a stream of character tokens had been seen + (see below for what they should say). */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if an end tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'form', + 'type' => HTML5::ENDTAG + ) + ); + } + break; + + /* A start tag whose tag name is "textarea" */ + case 'textarea': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the + RCDATA state. */ + return HTML5::RCDATA; + break; + + /* A start tag whose tag name is one of: "iframe", "noembed", + "noframes" */ + case 'iframe': + case 'noembed': + case 'noframes': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "select" */ + case 'select': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in select". */ + $this->mode = self::IN_SELECT; + break; + + /* A start or end tag whose tag name is one of: "caption", "col", + "colgroup", "frame", "frameset", "head", "option", "optgroup", + "tbody", "td", "tfoot", "th", "thead", "tr". */ + case 'caption': + case 'col': + case 'colgroup': + case 'frame': + case 'frameset': + case 'head': + case 'option': + case 'optgroup': + case 'tbody': + case 'td': + case 'tfoot': + case 'th': + case 'thead': + case 'tr': + // Parse error. Ignore the token. + break; + + /* A start or end tag whose tag name is one of: "event-source", + "section", "nav", "article", "aside", "header", "footer", + "datagrid", "command" */ + case 'event-source': + case 'section': + case 'nav': + case 'article': + case 'aside': + case 'header': + case 'footer': + case 'datagrid': + case 'command': + // Work in progress! + break; + + /* A start tag token not covered by the previous entries */ + default: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + $this->insertElement($token, true, true); + break; + } + break; + + case HTML5::ENDTAG: + switch ($token['name']) { + /* An end tag with the tag name "body" */ + case 'body': + /* If the second element in the stack of open elements is + not a body element, this is a parse error. Ignore the token. + (innerHTML case) */ + if (count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { + // Ignore. + + /* If the current node is not the body element, then this + is a parse error. */ + } elseif (end($this->stack)->nodeName !== 'body') { + // Parse error. + } + + /* Change the insertion mode to "after body". */ + $this->mode = self::AFTER_BODY; + break; + + /* An end tag with the tag name "html" */ + case 'html': + /* Act as if an end tag with tag name "body" had been seen, + then, if that token wasn't ignored, reprocess the current + token. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->afterBody($token); + break; + + /* An end tag whose tag name is one of: "address", "blockquote", + "center", "dir", "div", "dl", "fieldset", "listing", "menu", + "ol", "pre", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'pre': + case 'ul': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with + the same tag name as that of the token, then this + is a parse error. */ + // w/e + + /* If the stack of open elements has an element in + scope with the same tag name as that of the token, + then pop elements from this stack until an element + with that tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is "form" */ + case 'form': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + } + + if (end($this->stack)->nodeName !== $token['name']) { + /* Now, if the current node is not an element with the + same tag name as that of the token, then this is a parse + error. */ + // w/e + + } else { + /* Otherwise, if the current node is an element with + the same tag name as that of the token pop that element + from the stack. */ + array_pop($this->stack); + } + + /* In any case, set the form element pointer to null. */ + $this->form_pointer = null; + break; + + /* An end tag whose tag name is "p" */ + case 'p': + /* If the stack of open elements has a p element in scope, + then generate implied end tags, except for p elements. */ + if ($this->elementInScope('p')) { + $this->generateImpliedEndTags(array('p')); + + /* If the current node is not a p element, then this is + a parse error. */ + // k + + /* If the stack of open elements has a p element in + scope, then pop elements from this stack until the stack + no longer has a p element in scope. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->elementInScope('p')) { + array_pop($this->stack); + + } else { + break; + } + } + } + break; + + /* An end tag whose tag name is "dd", "dt", or "li" */ + case 'dd': + case 'dt': + case 'li': + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + generate implied end tags, except for elements with the + same tag name as the token. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(array($token['name'])); + + /* If the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + pop elements from this stack until an element with that + tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + generate implied end tags. */ + if ($this->elementInScope($elements)) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as that of the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has in scope an element + whose tag name is one of "h1", "h2", "h3", "h4", "h5", or + "h6", then pop elements from the stack until an element + with one of those tag names has been popped from the stack. */ + while ($this->elementInScope($elements)) { + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "a", "b", "big", "em", + "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'a': + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* 1. Let the formatting element be the last element in + the list of active formatting elements that: + * is between the end of the list and the last scope + marker in the list, if any, or the start of the list + otherwise, and + * has the same tag name as the token. + */ + while (true) { + for ($a = count($this->a_formatting) - 1; $a >= 0; $a--) { + if ($this->a_formatting[$a] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$a]->tagName === $token['name']) { + $formatting_element = $this->a_formatting[$a]; + $in_stack = in_array($formatting_element, $this->stack, true); + $fe_af_pos = $a; + break; + } + } + + /* If there is no such node, or, if that node is + also in the stack of open elements but the element + is not in scope, then this is a parse error. Abort + these steps. The token is ignored. */ + if (!isset($formatting_element) || ($in_stack && + !$this->elementInScope($token['name'])) + ) { + break; + + /* Otherwise, if there is such a node, but that node + is not in the stack of open elements, then this is a + parse error; remove the element from the list, and + abort these steps. */ + } elseif (isset($formatting_element) && !$in_stack) { + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 2. Let the furthest block be the topmost node in the + stack of open elements that is lower in the stack + than the formatting element, and is not an element in + the phrasing or formatting categories. There might + not be one. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $length = count($this->stack); + + for ($s = $fe_s_pos + 1; $s < $length; $s++) { + $category = $this->getElementCategory($this->stack[$s]->nodeName); + + if ($category !== self::PHRASING && $category !== self::FORMATTING) { + $furthest_block = $this->stack[$s]; + } + } + + /* 3. If there is no furthest block, then the UA must + skip the subsequent steps and instead just pop all + the nodes from the bottom of the stack of open + elements, from the current node up to the formatting + element, and remove the formatting element from the + list of active formatting elements. */ + if (!isset($furthest_block)) { + for ($n = $length - 1; $n >= $fe_s_pos; $n--) { + array_pop($this->stack); + } + + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 4. Let the common ancestor be the element + immediately above the formatting element in the stack + of open elements. */ + $common_ancestor = $this->stack[$fe_s_pos - 1]; + + /* 5. If the furthest block has a parent node, then + remove the furthest block from its parent node. */ + if ($furthest_block->parentNode !== null) { + $furthest_block->parentNode->removeChild($furthest_block); + } + + /* 6. Let a bookmark note the position of the + formatting element in the list of active formatting + elements relative to the elements on either side + of it in the list. */ + $bookmark = $fe_af_pos; + + /* 7. Let node and last node be the furthest block. + Follow these steps: */ + $node = $furthest_block; + $last_node = $furthest_block; + + while (true) { + for ($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { + /* 7.1 Let node be the element immediately + prior to node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 7.2 If node is not in the list of active + formatting elements, then remove node from + the stack of open elements and then go back + to step 1. */ + if (!in_array($node, $this->a_formatting, true)) { + unset($this->stack[$n]); + $this->stack = array_merge($this->stack); + + } else { + break; + } + } + + /* 7.3 Otherwise, if node is the formatting + element, then go to the next step in the overall + algorithm. */ + if ($node === $formatting_element) { + break; + + /* 7.4 Otherwise, if last node is the furthest + block, then move the aforementioned bookmark to + be immediately after the node in the list of + active formatting elements. */ + } elseif ($last_node === $furthest_block) { + $bookmark = array_search($node, $this->a_formatting, true) + 1; + } + + /* 7.5 If node has any children, perform a + shallow clone of node, replace the entry for + node in the list of active formatting elements + with an entry for the clone, replace the entry + for node in the stack of open elements with an + entry for the clone, and let node be the clone. */ + if ($node->hasChildNodes()) { + $clone = $node->cloneNode(); + $s_pos = array_search($node, $this->stack, true); + $a_pos = array_search($node, $this->a_formatting, true); + + $this->stack[$s_pos] = $clone; + $this->a_formatting[$a_pos] = $clone; + $node = $clone; + } + + /* 7.6 Insert last node into node, first removing + it from its previous parent node if any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $node->appendChild($last_node); + + /* 7.7 Let last node be node. */ + $last_node = $node; + } + + /* 8. Insert whatever last node ended up being in + the previous step into the common ancestor node, + first removing it from its previous parent node if + any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $common_ancestor->appendChild($last_node); + + /* 9. Perform a shallow clone of the formatting + element. */ + $clone = $formatting_element->cloneNode(); + + /* 10. Take all of the child nodes of the furthest + block and append them to the clone created in the + last step. */ + while ($furthest_block->hasChildNodes()) { + $child = $furthest_block->firstChild; + $furthest_block->removeChild($child); + $clone->appendChild($child); + } + + /* 11. Append that clone to the furthest block. */ + $furthest_block->appendChild($clone); + + /* 12. Remove the formatting element from the list + of active formatting elements, and insert the clone + into the list of active formatting elements at the + position of the aforementioned bookmark. */ + $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + + $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); + $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); + $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); + + /* 13. Remove the formatting element from the stack + of open elements, and insert the clone into the stack + of open elements immediately after (i.e. in a more + deeply nested position than) the position of the + furthest block in that stack. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $fb_s_pos = array_search($furthest_block, $this->stack, true); + unset($this->stack[$fe_s_pos]); + + $s_part1 = array_slice($this->stack, 0, $fb_s_pos); + $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); + $this->stack = array_merge($s_part1, array($clone), $s_part2); + + /* 14. Jump back to step 1 in this series of steps. */ + unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); + } + break; + + /* An end tag token whose tag name is one of: "button", + "marquee", "object" */ + case 'button': + case 'marquee': + case 'object': + /* If the stack of open elements has an element in scope whose + tag name matches the tag name of the token, then generate implied + tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // k + + /* Now, if the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then pop + elements from the stack until that element has been popped from + the stack, and clear the list of active formatting elements up + to the last marker. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + + $marker = end(array_keys($this->a_formatting, self::MARKER, true)); + + for ($n = count($this->a_formatting) - 1; $n > $marker; $n--) { + array_pop($this->a_formatting); + } + } + break; + + /* Or an end tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "hr", "iframe", "image", "img", + "input", "isindex", "noembed", "noframes", "param", "select", + "spacer", "table", "textarea", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'hr': + case 'iframe': + case 'image': + case 'img': + case 'input': + case 'isindex': + case 'noembed': + case 'noframes': + case 'param': + case 'select': + case 'spacer': + case 'table': + case 'textarea': + case 'wbr': + // Parse error. Ignore the token. + break; + + /* An end tag token not covered by the previous entries */ + default: + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + /* Initialise node to be the current node (the bottommost + node of the stack). */ + $node = end($this->stack); + + /* If node has the same tag name as the end tag token, + then: */ + if ($token['name'] === $node->nodeName) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* If the tag name of the end tag token does not + match the tag name of the current node, this is a + parse error. */ + // k + + /* Pop all the nodes from the current node up to + node, including node, then stop this algorithm. */ + for ($x = count($this->stack) - $n; $x >= $n; $x--) { + array_pop($this->stack); + } + + } else { + $category = $this->getElementCategory($node); + + if ($category !== self::SPECIAL && $category !== self::SCOPING) { + /* Otherwise, if node is in neither the formatting + category nor the phrasing category, then this is a + parse error. Stop this algorithm. The end tag token + is ignored. */ + return false; + } + } + } + break; + } + break; + } + } + + private function inTable($token) + { + $clear = array('html', 'table'); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "caption" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'caption' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + + /* Insert an HTML element for the token, then switch the + insertion mode to "in caption". */ + $this->insertElement($token); + $this->mode = self::IN_CAPTION; + + /* A start tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'colgroup' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the + insertion mode to "in column group". */ + $this->insertElement($token); + $this->mode = self::IN_CGROUP; + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'col' + ) { + $this->inTable( + array( + 'name' => 'colgroup', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + $this->inColumnGroup($token); + + /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('tbody', 'tfoot', 'thead') + ) + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in table body". */ + $this->insertElement($token); + $this->mode = self::IN_TBODY; + + /* A start tag whose tag name is one of: "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && + in_array($token['name'], array('td', 'th', 'tr')) + ) { + /* Act as if a start tag token with the tag name "tbody" had been + seen, then reprocess the current token. */ + $this->inTable( + array( + 'name' => 'tbody', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inTableBody($token); + + /* A start tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'table' + ) { + /* Parse error. Act as if an end tag token with the tag name "table" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inTable( + array( + 'name' => 'table', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + + /* An end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + return false; + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a table element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a table element has been + popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'table') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'caption', + 'col', + 'colgroup', + 'html', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Parse error. Process the token as if the insertion mode was "in + body", with the following exception: */ + + /* If the current node is a table, tbody, tfoot, thead, or tr + element, then, whenever a node would be inserted into the current + node, it must instead be inserted into the foster parent element. */ + if (in_array( + end($this->stack)->nodeName, + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* The foster parent element is the parent element of the last + table element in the stack of open elements, if there is a + table element and it has such a parent element. If there is no + table element in the stack of open elements (innerHTML case), + then the foster parent element is the first element in the + stack of open elements (the html element). Otherwise, if there + is a table element in the stack of open elements, but the last + table element in the stack of open elements has no parent, or + its parent node is not an element, then the foster parent + element is the element before the last table element in the + stack of open elements. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table') { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $table->parentNode !== null) { + $this->foster_parent = $table->parentNode; + + } elseif (!isset($table)) { + $this->foster_parent = $this->stack[0]; + + } elseif (isset($table) && ($table->parentNode === null || + $table->parentNode->nodeType !== XML_ELEMENT_NODE) + ) { + $this->foster_parent = $this->stack[$n - 1]; + } + } + + $this->inBody($token); + } + } + + private function inCaption($token) + { + /* An end tag whose tag name is "caption" */ + if ($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a caption element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a caption element has + been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === 'caption') { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag + name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + )) || ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table') + ) { + /* Parse error. Act as if an end tag with the tag name "caption" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inCaption( + array( + 'name' => 'caption', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + + /* An end tag whose tag name is one of: "body", "col", "colgroup", + "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'col', + 'colgroup', + 'html', + 'tbody', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inColumnGroup($token) + { + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { + /* Insert a col element for the token. Immediately pop the current + node off the stack of open elements. */ + $this->insertElement($token); + array_pop($this->stack); + + /* An end tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'colgroup' + ) { + /* If the current node is the root html element, then this is a + parse error, ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + /* Otherwise, pop the current node (which will be a colgroup + element) from the stack of open elements. Switch the insertion + mode to "in table". */ + } else { + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* An end tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Act as if an end tag with the tag name "colgroup" had been seen, + and then, if that token wasn't ignored, reprocess the current token. */ + $this->inColumnGroup( + array( + 'name' => 'colgroup', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + } + } + + private function inTableBody($token) + { + $clear = array('tbody', 'tfoot', 'thead', 'html'); + + /* A start tag whose tag name is "tr" */ + if ($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Insert a tr element for the token, then switch the insertion + mode to "in row". */ + $this->insertElement($token); + $this->mode = self::IN_ROW; + + /* A start tag whose tag name is one of: "th", "td" */ + } elseif ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Parse error. Act as if a start tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inTableBody( + array( + 'name' => 'tr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inRow($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node from the stack of open elements. Switch + the insertion mode to "in table". */ + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead') + )) || + ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table') + ) { + /* If the stack of open elements does not have a tbody, thead, or + tfoot element in table scope, this is a parse error. Ignore the + token. (innerHTML case) */ + if (!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Act as if an end tag with the same tag name as the current + node ("tbody", "tfoot", or "thead") had been seen, then + reprocess the current token. */ + $this->inTableBody( + array( + 'name' => end($this->stack)->nodeName, + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inRow($token) + { + $clear = array('tr', 'html'); + + /* A start tag whose tag name is one of: "th", "td" */ + if ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in cell". */ + $this->insertElement($token); + $this->mode = self::IN_CELL; + + /* Insert a marker at the end of the list of active formatting + elements. */ + $this->a_formatting[] = self::MARKER; + + /* An end tag whose tag name is "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node (which will be a tr element) from the + stack of open elements. Switch the insertion mode to "in table + body". */ + array_pop($this->stack); + $this->mode = self::IN_TBODY; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* Act as if an end tag with the tag name "tr" had been seen, then, + if that token wasn't ignored, reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Otherwise, act as if an end tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inCell($token) + { + /* An end tag whose tag name is one of: "td", "th" */ + if ($token['type'] === HTML5::ENDTAG && + ($token['name'] === 'td' || $token['name'] === 'th') + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token, then this is a + parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Generate implied end tags, except for elements with the same + tag name as the token. */ + $this->generateImpliedEndTags(array($token['name'])); + + /* Now, if the current node is not an element with the same tag + name as the token, then this is a parse error. */ + // k + + /* Pop elements from this stack until an element with the same + tag name as the token has been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === $token['name']) { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in row". (The current node + will be a tr element at this point.) */ + $this->mode = self::IN_ROW; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html') + ) + ) { + /* Parse error. Ignore the token. */ + + /* An end tag whose tag name is one of: "table", "tbody", "tfoot", + "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token (which can only + happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), + then this is a parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inSelect($token) + { + /* Handle the token as follows: */ + + /* A character token */ + if ($token['type'] === HTML5::CHARACTR) { + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* A start tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'optgroup' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, act as if an end tag + with the tag name "optgroup" had been seen. */ + if (end($this->stack)->nodeName === 'optgroup') { + $this->inSelect( + array( + 'name' => 'optgroup', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* An end tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'optgroup' + ) { + /* First, if the current node is an option element, and the node + immediately before it in the stack of open elements is an optgroup + element, then act as if an end tag with the tag name "option" had + been seen. */ + $elements_in_stack = count($this->stack); + + if ($this->stack[$elements_in_stack - 1]->nodeName === 'option' && + $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup' + ) { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if ($this->stack[$elements_in_stack - 1] === 'optgroup') { + array_pop($this->stack); + } + + /* An end tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if (end($this->stack)->nodeName === 'option') { + array_pop($this->stack); + } + + /* An end tag whose tag name is "select" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'select' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // w/e + + /* Otherwise: */ + } else { + /* Pop elements from the stack of open elements until a select + element has been popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'select') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* A start tag whose tag name is "select" */ + } elseif ($token['name'] === 'select' && + $token['type'] === HTML5::STARTTAG + ) { + /* Parse error. Act as if the token had been an end tag with the + tag name "select" instead. */ + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + /* An end tag whose tag name is one of: "caption", "table", "tbody", + "tfoot", "thead", "tr", "td", "th" */ + } elseif (in_array( + $token['name'], + array( + 'caption', + 'table', + 'tbody', + 'tfoot', + 'thead', + 'tr', + 'td', + 'th' + ) + ) && $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. */ + // w/e + + /* If the stack of open elements has an element in table scope with + the same tag name as that of the token, then act as if an end tag + with the tag name "select" had been seen, and reprocess the token. + Otherwise, ignore the token. */ + if ($this->elementInScope($token['name'], true)) { + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + $this->mainPhase($token); + } + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterBody($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed if the insertion mode + was "in body". */ + $this->inBody($token); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the first element in the stack of open + elements (the html element), with the data attribute set to the + data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->stack[0]->appendChild($comment); + + /* An end tag with the tag name "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { + /* If the parser was originally created in order to handle the + setting of an element's innerHTML attribute, this is a parse error; + ignore the token. (The element will be an html element in this + case.) (innerHTML case) */ + + /* Otherwise, switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* Anything else */ + } else { + /* Parse error. Set the insertion mode to "in body" and reprocess + the token. */ + $this->mode = self::IN_BODY; + return $this->inBody($token); + } + } + + private function inFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::STARTTAG + ) { + $this->insertElement($token); + + /* An end tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::ENDTAG + ) { + /* If the current node is the root html element, then this is a + parse error; ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + } else { + /* Otherwise, pop the current node from the stack of open + elements. */ + array_pop($this->stack); + + /* If the parser was not originally created in order to handle + the setting of an element's innerHTML attribute (innerHTML case), + and the current node is no longer a frameset element, then change + the insertion mode to "after frameset". */ + $this->mode = self::AFTR_FRAME; + } + + /* A start tag with the tag name "frame" */ + } elseif ($token['name'] === 'frame' && + $token['type'] === HTML5::STARTTAG + ) { + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* An end tag with the tag name "html" */ + } elseif ($token['name'] === 'html' && + $token['type'] === HTML5::ENDTAG + ) { + /* Switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function trailingEndPhase($token) + { + /* After the main phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed in the main phase. */ + $this->mainPhase($token); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or a start tag token. Or an end tag token. */ + } elseif (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. Switch back to the main phase and reprocess the + token. */ + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* OMG DONE!! */ + } + } + + private function insertElement($token, $append = true, $check = false) + { + // Proprietary workaround for libxml2's limitations with tag names + if ($check) { + // Slightly modified HTML5 tag-name modification, + // removing anything that's not an ASCII letter, digit, or hyphen + $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); + // Remove leading hyphens and numbers + $token['name'] = ltrim($token['name'], '-0..9'); + // In theory, this should ever be needed, but just in case + if ($token['name'] === '') { + $token['name'] = 'span'; + } // arbitrary generic choice + } + + $el = $this->dom->createElement($token['name']); + + foreach ($token['attr'] as $attr) { + if (!$el->hasAttribute($attr['name'])) { + $el->setAttribute($attr['name'], $attr['value']); + } + } + + $this->appendToRealParent($el); + $this->stack[] = $el; + + return $el; + } + + private function insertText($data) + { + $text = $this->dom->createTextNode($data); + $this->appendToRealParent($text); + } + + private function insertComment($data) + { + $comment = $this->dom->createComment($data); + $this->appendToRealParent($comment); + } + + private function appendToRealParent($node) + { + if ($this->foster_parent === null) { + end($this->stack)->appendChild($node); + + } elseif ($this->foster_parent !== null) { + /* If the foster parent element is the parent element of the + last table element in the stack of open elements, then the new + node must be inserted immediately before the last table element + in the stack of open elements in the foster parent element; + otherwise, the new node must be appended to the foster parent + element. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table' && + $this->stack[$n]->parentNode !== null + ) { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $this->foster_parent->isSameNode($table->parentNode)) { + $this->foster_parent->insertBefore($node, $table); + } else { + $this->foster_parent->appendChild($node); + } + + $this->foster_parent = null; + } + } + + private function elementInScope($el, $table = false) + { + if (is_array($el)) { + foreach ($el as $element) { + if ($this->elementInScope($element, $table)) { + return true; + } + } + + return false; + } + + $leng = count($this->stack); + + for ($n = 0; $n < $leng; $n++) { + /* 1. Initialise node to be the current node (the bottommost node of + the stack). */ + $node = $this->stack[$leng - 1 - $n]; + + if ($node->tagName === $el) { + /* 2. If node is the target node, terminate in a match state. */ + return true; + + } elseif ($node->tagName === 'table') { + /* 3. Otherwise, if node is a table element, terminate in a failure + state. */ + return false; + + } elseif ($table === true && in_array( + $node->tagName, + array( + 'caption', + 'td', + 'th', + 'button', + 'marquee', + 'object' + ) + ) + ) { + /* 4. Otherwise, if the algorithm is the "has an element in scope" + variant (rather than the "has an element in table scope" variant), + and node is one of the following, terminate in a failure state. */ + return false; + + } elseif ($node === $node->ownerDocument->documentElement) { + /* 5. Otherwise, if node is an html element (root element), terminate + in a failure state. (This can only happen if the node is the topmost + node of the stack of open elements, and prevents the next step from + being invoked if there are no more elements in the stack.) */ + return false; + } + + /* Otherwise, set node to the previous entry in the stack of open + elements and return to step 2. (This will never fail, since the loop + will always terminate in the previous step if the top of the stack + is reached.) */ + } + } + + private function reconstructActiveFormattingElements() + { + /* 1. If there are no entries in the list of active formatting elements, + then there is nothing to reconstruct; stop this algorithm. */ + $formatting_elements = count($this->a_formatting); + + if ($formatting_elements === 0) { + return false; + } + + /* 3. Let entry be the last (most recently added) element in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. If the last (most recently added) entry in the list of active + formatting elements is a marker, or if it is an element that is in the + stack of open elements, then there is nothing to reconstruct; stop this + algorithm. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + return false; + } + + for ($a = $formatting_elements - 1; $a >= 0; true) { + /* 4. If there are no entries before entry in the list of active + formatting elements, then jump to step 8. */ + if ($a === 0) { + $step_seven = false; + break; + } + + /* 5. Let entry be the entry one earlier than entry in the list of + active formatting elements. */ + $a--; + $entry = $this->a_formatting[$a]; + + /* 6. If entry is neither a marker nor an element that is also in + thetack of open elements, go to step 4. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + break; + } + } + + while (true) { + /* 7. Let entry be the element one later than entry in the list of + active formatting elements. */ + if (isset($step_seven) && $step_seven === true) { + $a++; + $entry = $this->a_formatting[$a]; + } + + /* 8. Perform a shallow clone of the element entry to obtain clone. */ + $clone = $entry->cloneNode(); + + /* 9. Append clone to the current node and push it onto the stack + of open elements so that it is the new current node. */ + end($this->stack)->appendChild($clone); + $this->stack[] = $clone; + + /* 10. Replace the entry for entry in the list with an entry for + clone. */ + $this->a_formatting[$a] = $clone; + + /* 11. If the entry for clone in the list of active formatting + elements is not the last entry in the list, return to step 7. */ + if (end($this->a_formatting) !== $clone) { + $step_seven = true; + } else { + break; + } + } + } + + private function clearTheActiveFormattingElementsUpToTheLastMarker() + { + /* When the steps below require the UA to clear the list of active + formatting elements up to the last marker, the UA must perform the + following steps: */ + + while (true) { + /* 1. Let entry be the last (most recently added) entry in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. Remove entry from the list of active formatting elements. */ + array_pop($this->a_formatting); + + /* 3. If entry was a marker, then stop the algorithm at this point. + The list has been cleared up to the last marker. */ + if ($entry === self::MARKER) { + break; + } + } + } + + private function generateImpliedEndTags($exclude = array()) + { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must + act as if an end tag with the respective tag name had been seen and + then generate implied end tags again. */ + $node = end($this->stack); + $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); + + while (in_array(end($this->stack)->nodeName, $elements)) { + array_pop($this->stack); + } + } + + private function getElementCategory($node) + { + $name = $node->tagName; + if (in_array($name, $this->special)) { + return self::SPECIAL; + } elseif (in_array($name, $this->scoping)) { + return self::SCOPING; + } elseif (in_array($name, $this->formatting)) { + return self::FORMATTING; + } else { + return self::PHRASING; + } + } + + private function clearStackToTableContext($elements) + { + /* When the steps above require the UA to clear the stack back to a + table context, it means that the UA must, while the current node is not + a table element or an html element, pop elements from the stack of open + elements. If this causes any elements to be popped from the stack, then + this is a parse error. */ + while (true) { + $node = end($this->stack)->nodeName; + + if (in_array($node, $elements)) { + break; + } else { + array_pop($this->stack); + } + } + } + + private function resetInsertionMode() + { + /* 1. Let last be false. */ + $last = false; + $leng = count($this->stack); + + for ($n = $leng - 1; $n >= 0; $n--) { + /* 2. Let node be the last node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 3. If node is the first node in the stack of open elements, then + set last to true. If the element whose innerHTML attribute is being + set is neither a td element nor a th element, then set node to the + element whose innerHTML attribute is being set. (innerHTML case) */ + if ($this->stack[0]->isSameNode($node)) { + $last = true; + } + + /* 4. If node is a select element, then switch the insertion mode to + "in select" and abort these steps. (innerHTML case) */ + if ($node->nodeName === 'select') { + $this->mode = self::IN_SELECT; + break; + + /* 5. If node is a td or th element, then switch the insertion mode + to "in cell" and abort these steps. */ + } elseif ($node->nodeName === 'td' || $node->nodeName === 'th') { + $this->mode = self::IN_CELL; + break; + + /* 6. If node is a tr element, then switch the insertion mode to + "in row" and abort these steps. */ + } elseif ($node->nodeName === 'tr') { + $this->mode = self::IN_ROW; + break; + + /* 7. If node is a tbody, thead, or tfoot element, then switch the + insertion mode to "in table body" and abort these steps. */ + } elseif (in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { + $this->mode = self::IN_TBODY; + break; + + /* 8. If node is a caption element, then switch the insertion mode + to "in caption" and abort these steps. */ + } elseif ($node->nodeName === 'caption') { + $this->mode = self::IN_CAPTION; + break; + + /* 9. If node is a colgroup element, then switch the insertion mode + to "in column group" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'colgroup') { + $this->mode = self::IN_CGROUP; + break; + + /* 10. If node is a table element, then switch the insertion mode + to "in table" and abort these steps. */ + } elseif ($node->nodeName === 'table') { + $this->mode = self::IN_TABLE; + break; + + /* 11. If node is a head element, then switch the insertion mode + to "in body" ("in body"! not "in head"!) and abort these steps. + (innerHTML case) */ + } elseif ($node->nodeName === 'head') { + $this->mode = self::IN_BODY; + break; + + /* 12. If node is a body element, then switch the insertion mode to + "in body" and abort these steps. */ + } elseif ($node->nodeName === 'body') { + $this->mode = self::IN_BODY; + break; + + /* 13. If node is a frameset element, then switch the insertion + mode to "in frameset" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'frameset') { + $this->mode = self::IN_FRAME; + break; + + /* 14. If node is an html element, then: if the head element + pointer is null, switch the insertion mode to "before head", + otherwise, switch the insertion mode to "after head". In either + case, abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'html') { + $this->mode = ($this->head_pointer === null) + ? self::BEFOR_HEAD + : self::AFTER_HEAD; + + break; + + /* 15. If last is true, then set the insertion mode to "in body" + and abort these steps. (innerHTML case) */ + } elseif ($last) { + $this->mode = self::IN_BODY; + break; + } + } + } + + private function closeCell() + { + /* If the stack of open elements has a td or th element in table scope, + then act as if an end tag token with that tag name had been seen. */ + foreach (array('td', 'th') as $cell) { + if ($this->elementInScope($cell, true)) { + $this->inCell( + array( + 'name' => $cell, + 'type' => HTML5::ENDTAG + ) + ); + + break; + } + } + } + + public function save() + { + return $this->dom; + } +} diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Node.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node.php new file mode 100644 index 0000000000..3995fec9fe --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node.php @@ -0,0 +1,49 @@ +<?php + +/** + * Abstract base node class that all others inherit from. + * + * Why do we not use the DOM extension? (1) It is not always available, + * (2) it has funny constraints on the data it can represent, + * whereas we want a maximally flexible representation, and (3) its + * interface is a bit cumbersome. + */ +abstract class HTMLPurifier_Node +{ + /** + * Line number of the start token in the source document + * @type int + */ + public $line; + + /** + * Column number of the start token in the source document. Null if unknown. + * @type int + */ + public $col; + + /** + * Lookup array of processing that this token is exempt from. + * Currently, valid values are "ValidateAttributes". + * @type array + */ + public $armor = array(); + + /** + * When true, this node should be ignored as non-existent. + * + * Who is responsible for ignoring dead nodes? FixNesting is + * responsible for removing them before passing on to child + * validators. + */ + public $dead = false; + + /** + * Returns a pair of start and end tokens, where the end token + * is null if it is not necessary. Does not include children. + * @type array + */ + abstract public function toTokenPair(); +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php new file mode 100644 index 0000000000..38ba193942 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Comment.php @@ -0,0 +1,36 @@ +<?php + +/** + * Concrete comment node class. + */ +class HTMLPurifier_Node_Comment extends HTMLPurifier_Node +{ + /** + * Character data within comment. + * @type string + */ + public $data; + + /** + * @type bool + */ + public $is_whitespace = true; + + /** + * Transparent constructor. + * + * @param string $data String comment data. + * @param int $line + * @param int $col + */ + public function __construct($data, $line = null, $col = null) + { + $this->data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null); + } +} diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php new file mode 100644 index 0000000000..6cbf56dada --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php @@ -0,0 +1,59 @@ +<?php + +/** + * Concrete element node class. + */ +class HTMLPurifier_Node_Element extends HTMLPurifier_Node +{ + /** + * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. + * + * @note Strictly speaking, XML tags are case sensitive, so we shouldn't + * be lower-casing them, but these tokens cater to HTML tags, which are + * insensitive. + * @type string + */ + public $name; + + /** + * Associative array of the node's attributes. + * @type array + */ + public $attr = array(); + + /** + * List of child elements. + * @type array + */ + public $children = array(); + + /** + * Does this use the <a></a> form or the </a> form, i.e. + * is it a pair of start/end tokens or an empty token. + * @bool + */ + public $empty = false; + + public $endCol = null, $endLine = null, $endArmor = array(); + + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { + $this->name = $name; + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toTokenPair() { + // XXX inefficiency here, normalization is not necessary + if ($this->empty) { + return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null); + } else { + $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor); + $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor); + //$end->start = $start; + return array($start, $end); + } + } +} + diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php new file mode 100644 index 0000000000..aec9166473 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php @@ -0,0 +1,54 @@ +<?php + +/** + * Concrete text token class. + * + * Text tokens comprise of regular parsed character data (PCDATA) and raw + * character data (from the CDATA sections). Internally, their + * data is parsed with all entities expanded. Surprisingly, the text token + * does have a "tag name" called #PCDATA, which is how the DTD represents it + * in permissible child nodes. + */ +class HTMLPurifier_Node_Text extends HTMLPurifier_Node +{ + + /** + * PCDATA tag name compatible with DTD, see + * HTMLPurifier_ChildDef_Custom for details. + * @type string + */ + public $name = '#PCDATA'; + + /** + * @type string + */ + public $data; + /**< Parsed character data of text. */ + + /** + * @type bool + */ + public $is_whitespace; + + /**< Bool indicating if node is whitespace. */ + + /** + * Constructor, accepts data and determines if it is whitespace. + * @param string $data String parsed character data. + * @param int $line + * @param int $col + */ + public function __construct($data, $is_whitespace, $line = null, $col = null) + { + $this->data = $data; + $this->is_whitespace = $is_whitespace; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/PercentEncoder.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php similarity index 78% rename from library/HTMLPurifier/PercentEncoder.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php index a43c44f4c5..18c8bbb00a 100644 --- a/library/HTMLPurifier/PercentEncoder.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php @@ -13,17 +13,26 @@ class HTMLPurifier_PercentEncoder /** * Reserved characters to preserve when using encode(). + * @type array */ protected $preserve = array(); /** * String of characters that should be preserved while using encode(). + * @param bool $preserve */ - public function __construct($preserve = false) { + public function __construct($preserve = false) + { // unreserved letters, ought to const-ify - for ($i = 48; $i <= 57; $i++) $this->preserve[$i] = true; // digits - for ($i = 65; $i <= 90; $i++) $this->preserve[$i] = true; // upper-case - for ($i = 97; $i <= 122; $i++) $this->preserve[$i] = true; // lower-case + for ($i = 48; $i <= 57; $i++) { // digits + $this->preserve[$i] = true; + } + for ($i = 65; $i <= 90; $i++) { // upper-case + $this->preserve[$i] = true; + } + for ($i = 97; $i <= 122; $i++) { // lower-case + $this->preserve[$i] = true; + } $this->preserve[45] = true; // Dash - $this->preserve[46] = true; // Period . $this->preserve[95] = true; // Underscore _ @@ -44,13 +53,14 @@ class HTMLPurifier_PercentEncoder * Assumes that the string has already been normalized, making any * and all percent escape sequences valid. Percents will not be * re-escaped, regardless of their status in $preserve - * @param $string String to be encoded - * @return Encoded string. + * @param string $string String to be encoded + * @return string Encoded string. */ - public function encode($string) { + public function encode($string) + { $ret = ''; for ($i = 0, $c = strlen($string); $i < $c; $i++) { - if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])]) ) { + if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) { $ret .= '%' . sprintf('%02X', $int); } else { $ret .= $string[$i]; @@ -64,10 +74,14 @@ class HTMLPurifier_PercentEncoder * @warning This function is affected by $preserve, even though the * usual desired behavior is for this not to preserve those * characters. Be careful when reusing instances of PercentEncoder! - * @param $string String to normalize + * @param string $string String to normalize + * @return string */ - public function normalize($string) { - if ($string == '') return ''; + public function normalize($string) + { + if ($string == '') { + return ''; + } $parts = explode('%', $string); $ret = array_shift($parts); foreach ($parts as $part) { @@ -92,7 +106,6 @@ class HTMLPurifier_PercentEncoder } return $ret; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Printer.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php similarity index 54% rename from library/HTMLPurifier/Printer.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php index e7eb82e83e..549e4cea1e 100644 --- a/library/HTMLPurifier/Printer.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php @@ -7,25 +7,30 @@ class HTMLPurifier_Printer { /** - * Instance of HTMLPurifier_Generator for HTML generation convenience funcs + * For HTML generation convenience funcs. + * @type HTMLPurifier_Generator */ protected $generator; /** - * Instance of HTMLPurifier_Config, for easy access + * For easy access. + * @type HTMLPurifier_Config */ protected $config; /** * Initialize $generator. */ - public function __construct() { + public function __construct() + { } /** * Give generator necessary configuration if possible + * @param HTMLPurifier_Config $config */ - public function prepareGenerator($config) { + public function prepareGenerator($config) + { $all = $config->getAll(); $context = new HTMLPurifier_Context(); $this->generator = new HTMLPurifier_Generator($config, $context); @@ -39,45 +44,62 @@ class HTMLPurifier_Printer /** * Returns a start tag - * @param $tag Tag name - * @param $attr Attribute array + * @param string $tag Tag name + * @param array $attr Attribute array + * @return string */ - protected function start($tag, $attr = array()) { + protected function start($tag, $attr = array()) + { return $this->generator->generateFromToken( - new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) - ); + new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) + ); } /** - * Returns an end teg - * @param $tag Tag name + * Returns an end tag + * @param string $tag Tag name + * @return string */ - protected function end($tag) { + protected function end($tag) + { return $this->generator->generateFromToken( - new HTMLPurifier_Token_End($tag) - ); + new HTMLPurifier_Token_End($tag) + ); } /** * Prints a complete element with content inside - * @param $tag Tag name - * @param $contents Element contents - * @param $attr Tag attributes - * @param $escape Bool whether or not to escape contents + * @param string $tag Tag name + * @param string $contents Element contents + * @param array $attr Tag attributes + * @param bool $escape whether or not to escape contents + * @return string */ - protected function element($tag, $contents, $attr = array(), $escape = true) { + protected function element($tag, $contents, $attr = array(), $escape = true) + { return $this->start($tag, $attr) . - ($escape ? $this->escape($contents) : $contents) . - $this->end($tag); + ($escape ? $this->escape($contents) : $contents) . + $this->end($tag); } - protected function elementEmpty($tag, $attr = array()) { + /** + * @param string $tag + * @param array $attr + * @return string + */ + protected function elementEmpty($tag, $attr = array()) + { return $this->generator->generateFromToken( new HTMLPurifier_Token_Empty($tag, $attr) ); } - protected function text($text) { + /** + * @param string $text + * @return string + */ + protected function text($text) + { return $this->generator->generateFromToken( new HTMLPurifier_Token_Text($text) ); @@ -85,24 +107,29 @@ class HTMLPurifier_Printer /** * Prints a simple key/value row in a table. - * @param $name Key - * @param $value Value + * @param string $name Key + * @param mixed $value Value + * @return string */ - protected function row($name, $value) { - if (is_bool($value)) $value = $value ? 'On' : 'Off'; + protected function row($name, $value) + { + if (is_bool($value)) { + $value = $value ? 'On' : 'Off'; + } return $this->start('tr') . "\n" . - $this->element('th', $name) . "\n" . - $this->element('td', $value) . "\n" . - $this->end('tr') - ; + $this->element('th', $name) . "\n" . + $this->element('td', $value) . "\n" . + $this->end('tr'); } /** * Escapes a string for HTML output. - * @param $string String to escape + * @param string $string String to escape + * @return string */ - protected function escape($string) { + protected function escape($string) + { $string = HTMLPurifier_Encoder::cleanUTF8($string); $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); return $string; @@ -110,32 +137,46 @@ class HTMLPurifier_Printer /** * Takes a list of strings and turns them into a single list - * @param $array List of strings - * @param $polite Bool whether or not to add an end before the last + * @param string[] $array List of strings + * @param bool $polite Bool whether or not to add an end before the last + * @return string */ - protected function listify($array, $polite = false) { - if (empty($array)) return 'None'; + protected function listify($array, $polite = false) + { + if (empty($array)) { + return 'None'; + } $ret = ''; $i = count($array); foreach ($array as $value) { $i--; $ret .= $value; - if ($i > 0 && !($polite && $i == 1)) $ret .= ', '; - if ($polite && $i == 1) $ret .= 'and '; + if ($i > 0 && !($polite && $i == 1)) { + $ret .= ', '; + } + if ($polite && $i == 1) { + $ret .= 'and '; + } } return $ret; } /** * Retrieves the class of an object without prefixes, as well as metadata - * @param $obj Object to determine class of - * @param $prefix Further prefix to remove + * @param object $obj Object to determine class of + * @param string $sec_prefix Further prefix to remove + * @return string */ - protected function getClass($obj, $sec_prefix = '') { + protected function getClass($obj, $sec_prefix = '') + { static $five = null; - if ($five === null) $five = version_compare(PHP_VERSION, '5', '>='); + if ($five === null) { + $five = version_compare(PHP_VERSION, '5', '>='); + } $prefix = 'HTMLPurifier_' . $sec_prefix; - if (!$five) $prefix = strtolower($prefix); + if (!$five) { + $prefix = strtolower($prefix); + } $class = str_replace($prefix, '', get_class($obj)); $lclass = strtolower($class); $class .= '('; @@ -164,13 +205,14 @@ class HTMLPurifier_Printer break; case 'css_importantdecorator': $class .= $this->getClass($obj->def, $sec_prefix); - if ($obj->allow) $class .= ', !important'; + if ($obj->allow) { + $class .= ', !important'; + } break; } $class .= ')'; return $class; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Printer/CSSDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php similarity index 85% rename from library/HTMLPurifier/Printer/CSSDefinition.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php index 81f9865901..29505fe12d 100644 --- a/library/HTMLPurifier/Printer/CSSDefinition.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php @@ -2,10 +2,17 @@ class HTMLPurifier_Printer_CSSDefinition extends HTMLPurifier_Printer { - + /** + * @type HTMLPurifier_CSSDefinition + */ protected $def; - public function render($config) { + /** + * @param HTMLPurifier_Config $config + * @return string + */ + public function render($config) + { $this->def = $config->getCSSDefinition(); $ret = ''; @@ -32,7 +39,6 @@ class HTMLPurifier_Printer_CSSDefinition extends HTMLPurifier_Printer return $ret; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Printer/ConfigForm.css b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css similarity index 100% rename from library/HTMLPurifier/Printer/ConfigForm.css rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css diff --git a/library/HTMLPurifier/Printer/ConfigForm.js b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js similarity index 100% rename from library/HTMLPurifier/Printer/ConfigForm.js rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js diff --git a/library/HTMLPurifier/Printer/ConfigForm.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php similarity index 60% rename from library/HTMLPurifier/Printer/ConfigForm.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php index 02aa656894..36100ce738 100644 --- a/library/HTMLPurifier/Printer/ConfigForm.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php @@ -7,17 +7,20 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer { /** - * Printers for specific fields + * Printers for specific fields. + * @type HTMLPurifier_Printer[] */ protected $fields = array(); /** - * Documentation URL, can have fragment tagged on end + * Documentation URL, can have fragment tagged on end. + * @type string */ protected $docURL; /** - * Name of form element to stuff config in + * Name of form element to stuff config in. + * @type string */ protected $name; @@ -25,24 +28,27 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer * Whether or not to compress directive names, clipping them off * after a certain amount of letters. False to disable or integer letters * before clipping. + * @type bool */ protected $compress = false; /** - * @param $name Form element name for directives to be stuffed into - * @param $doc_url String documentation URL, will have fragment tagged on - * @param $compress Integer max length before compressing a directive name, set to false to turn off + * @param string $name Form element name for directives to be stuffed into + * @param string $doc_url String documentation URL, will have fragment tagged on + * @param bool $compress Integer max length before compressing a directive name, set to false to turn off */ public function __construct( - $name, $doc_url = null, $compress = false + $name, + $doc_url = null, + $compress = false ) { parent::__construct(); $this->docURL = $doc_url; - $this->name = $name; + $this->name = $name; $this->compress = $compress; // initialize sub-printers - $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); - $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); + $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); + $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); } /** @@ -50,32 +56,42 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer * @param $cols Integer columns of textarea, null to use default * @param $rows Integer rows of textarea, null to use default */ - public function setTextareaDimensions($cols = null, $rows = null) { - if ($cols) $this->fields['default']->cols = $cols; - if ($rows) $this->fields['default']->rows = $rows; + public function setTextareaDimensions($cols = null, $rows = null) + { + if ($cols) { + $this->fields['default']->cols = $cols; + } + if ($rows) { + $this->fields['default']->rows = $rows; + } } /** * Retrieves styling, in case it is not accessible by webserver */ - public static function getCSS() { + public static function getCSS() + { return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css'); } /** * Retrieves JavaScript, in case it is not accessible by webserver */ - public static function getJavaScript() { + public static function getJavaScript() + { return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); } /** * Returns HTML output for a configuration form - * @param $config Configuration object of current form state, or an array + * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array * where [0] has an HTML namespace and [1] is being rendered. - * @param $allowed Optional namespace(s) and directives to restrict form to. + * @param array|bool $allowed Optional namespace(s) and directives to restrict form to. + * @param bool $render_controls + * @return string */ - public function render($config, $allowed = true, $render_controls = true) { + public function render($config, $allowed = true, $render_controls = true) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -91,29 +107,29 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer $all = array(); foreach ($allowed as $key) { list($ns, $directive) = $key; - $all[$ns][$directive] = $config->get($ns .'.'. $directive); + $all[$ns][$directive] = $config->get($ns . '.' . $directive); } $ret = ''; $ret .= $this->start('table', array('class' => 'hp-config')); $ret .= $this->start('thead'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive')); - $ret .= $this->element('th', 'Value', array('class' => 'hp-value')); + $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive')); + $ret .= $this->element('th', 'Value', array('class' => 'hp-value')); $ret .= $this->end('tr'); $ret .= $this->end('thead'); foreach ($all as $ns => $directives) { $ret .= $this->renderNamespace($ns, $directives); } if ($render_controls) { - $ret .= $this->start('tbody'); - $ret .= $this->start('tr'); - $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls')); - $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit')); - $ret .= '[<a href="?">Reset</a>]'; - $ret .= $this->end('td'); - $ret .= $this->end('tr'); - $ret .= $this->end('tbody'); + $ret .= $this->start('tbody'); + $ret .= $this->start('tr'); + $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls')); + $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit')); + $ret .= '[<a href="?">Reset</a>]'; + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); } $ret .= $this->end('table'); return $ret; @@ -122,13 +138,15 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer /** * Renders a single namespace * @param $ns String namespace name - * @param $directive Associative array of directives to values + * @param array $directives array of directives to values + * @return string */ - protected function renderNamespace($ns, $directives) { + protected function renderNamespace($ns, $directives) + { $ret = ''; $ret .= $this->start('tbody', array('class' => 'namespace')); $ret .= $this->start('tr'); - $ret .= $this->element('th', $ns, array('colspan' => 2)); + $ret .= $this->element('th', $ns, array('colspan' => 2)); $ret .= $this->end('tr'); $ret .= $this->end('tbody'); $ret .= $this->start('tbody'); @@ -139,40 +157,44 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL); $ret .= $this->start('a', array('href' => $url)); } - $attr = array('for' => "{$this->name}:$ns.$directive"); + $attr = array('for' => "{$this->name}:$ns.$directive"); - // crop directive name if it's too long - if (!$this->compress || (strlen($directive) < $this->compress)) { - $directive_disp = $directive; - } else { - $directive_disp = substr($directive, 0, $this->compress - 2) . '...'; - $attr['title'] = $directive; - } + // crop directive name if it's too long + if (!$this->compress || (strlen($directive) < $this->compress)) { + $directive_disp = $directive; + } else { + $directive_disp = substr($directive, 0, $this->compress - 2) . '...'; + $attr['title'] = $directive; + } - $ret .= $this->element( - 'label', - $directive_disp, - // component printers must create an element with this id - $attr - ); - if ($this->docURL) $ret .= $this->end('a'); + $ret .= $this->element( + 'label', + $directive_disp, + // component printers must create an element with this id + $attr + ); + if ($this->docURL) { + $ret .= $this->end('a'); + } $ret .= $this->end('th'); $ret .= $this->start('td'); - $def = $this->config->def->info["$ns.$directive"]; - if (is_int($def)) { - $allow_null = $def < 0; - $type = abs($def); - } else { - $type = $def->type; - $allow_null = isset($def->allow_null); - } - if (!isset($this->fields[$type])) $type = 0; // default - $type_obj = $this->fields[$type]; - if ($allow_null) { - $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj); - } - $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config)); + $def = $this->config->def->info["$ns.$directive"]; + if (is_int($def)) { + $allow_null = $def < 0; + $type = abs($def); + } else { + $type = $def->type; + $allow_null = isset($def->allow_null); + } + if (!isset($this->fields[$type])) { + $type = 0; + } // default + $type_obj = $this->fields[$type]; + if ($allow_null) { + $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj); + } + $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config)); $ret .= $this->end('td'); $ret .= $this->end('tr'); } @@ -185,19 +207,33 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer /** * Printer decorator for directives that accept null */ -class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer { +class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer +{ /** * Printer being decorated + * @type HTMLPurifier_Printer */ protected $obj; + /** - * @param $obj Printer to decorate + * @param HTMLPurifier_Printer $obj Printer to decorate */ - public function __construct($obj) { + public function __construct($obj) + { parent::__construct(); $this->obj = $obj; } - public function render($ns, $directive, $value, $name, $config) { + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -215,15 +251,19 @@ class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer 'type' => 'checkbox', 'value' => '1', 'class' => 'null-toggle', - 'name' => "$name"."[Null_$ns.$directive]", + 'name' => "$name" . "[Null_$ns.$directive]", 'id' => "$name:Null_$ns.$directive", 'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!! ); if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) { // modify inline javascript slightly - $attr['onclick'] = "toggleWriteability('$name:Yes_$ns.$directive',checked);toggleWriteability('$name:No_$ns.$directive',checked)"; + $attr['onclick'] = + "toggleWriteability('$name:Yes_$ns.$directive',checked);" . + "toggleWriteability('$name:No_$ns.$directive',checked)"; + } + if ($value === null) { + $attr['checked'] = 'checked'; } - if ($value === null) $attr['checked'] = 'checked'; $ret .= $this->elementEmpty('input', $attr); $ret .= $this->text(' or '); $ret .= $this->elementEmpty('br'); @@ -235,10 +275,28 @@ class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer /** * Swiss-army knife configuration form field printer */ -class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer { +class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer +{ + /** + * @type int + */ public $cols = 18; + + /** + * @type int + */ public $rows = 5; - public function render($ns, $directive, $value, $name, $config) { + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -262,6 +320,7 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer { foreach ($array as $val => $b) { $value[] = $val; } + //TODO does this need a break? case HTMLPurifier_VarParser::ALIST: $value = implode(PHP_EOL, $value); break; @@ -281,25 +340,27 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer { $value = serialize($value); } $attr = array( - 'name' => "$name"."[$ns.$directive]", + 'name' => "$name" . "[$ns.$directive]", 'id' => "$name:$ns.$directive" ); - if ($value === null) $attr['disabled'] = 'disabled'; + if ($value === null) { + $attr['disabled'] = 'disabled'; + } if (isset($def->allowed)) { $ret .= $this->start('select', $attr); foreach ($def->allowed as $val => $b) { $attr = array(); - if ($value == $val) $attr['selected'] = 'selected'; + if ($value == $val) { + $attr['selected'] = 'selected'; + } $ret .= $this->element('option', $val, $attr); } $ret .= $this->end('select'); - } elseif ( - $type === HTMLPurifier_VarParser::TEXT || - $type === HTMLPurifier_VarParser::ITEXT || - $type === HTMLPurifier_VarParser::ALIST || - $type === HTMLPurifier_VarParser::HASH || - $type === HTMLPurifier_VarParser::LOOKUP - ) { + } elseif ($type === HTMLPurifier_VarParser::TEXT || + $type === HTMLPurifier_VarParser::ITEXT || + $type === HTMLPurifier_VarParser::ALIST || + $type === HTMLPurifier_VarParser::HASH || + $type === HTMLPurifier_VarParser::LOOKUP) { $attr['cols'] = $this->cols; $attr['rows'] = $this->rows; $ret .= $this->start('textarea', $attr); @@ -317,8 +378,18 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer { /** * Bool form field printer */ -class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer { - public function render($ns, $directive, $value, $name, $config) { +class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer +{ + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { if (is_array($config) && isset($config[0])) { $gen_config = $config[0]; $config = $config[1]; @@ -336,12 +407,16 @@ class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer { $attr = array( 'type' => 'radio', - 'name' => "$name"."[$ns.$directive]", + 'name' => "$name" . "[$ns.$directive]", 'id' => "$name:Yes_$ns.$directive", 'value' => '1' ); - if ($value === true) $attr['checked'] = 'checked'; - if ($value === null) $attr['disabled'] = 'disabled'; + if ($value === true) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } $ret .= $this->elementEmpty('input', $attr); $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive")); @@ -351,12 +426,16 @@ class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer { $attr = array( 'type' => 'radio', - 'name' => "$name"."[$ns.$directive]", + 'name' => "$name" . "[$ns.$directive]", 'id' => "$name:No_$ns.$directive", 'value' => '0' ); - if ($value === false) $attr['checked'] = 'checked'; - if ($value === null) $attr['disabled'] = 'disabled'; + if ($value === false) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } $ret .= $this->elementEmpty('input', $attr); $ret .= $this->end('div'); diff --git a/library/HTMLPurifier/Printer/HTMLDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php similarity index 53% rename from library/HTMLPurifier/Printer/HTMLDefinition.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php index 8a8f126b81..5f2f2f8a7c 100644 --- a/library/HTMLPurifier/Printer/HTMLDefinition.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php @@ -4,11 +4,16 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer { /** - * Instance of HTMLPurifier_HTMLDefinition, for easy access + * @type HTMLPurifier_HTMLDefinition, for easy access */ protected $def; - public function render($config) { + /** + * @param HTMLPurifier_Config $config + * @return string + */ + public function render($config) + { $ret = ''; $this->config =& $config; @@ -28,8 +33,10 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Renders the Doctype table + * @return string */ - protected function renderDoctype() { + protected function renderDoctype() + { $doctype = $this->def->doctype; $ret = ''; $ret .= $this->start('table'); @@ -45,8 +52,10 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Renders environment table, which is miscellaneous info + * @return string */ - protected function renderEnvironment() { + protected function renderEnvironment() + { $def = $this->def; $ret = ''; @@ -59,28 +68,28 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer $ret .= $this->row('Block wrap name', $def->info_block_wrapper); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Global attributes'); - $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr),0,0); + $ret .= $this->element('th', 'Global attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Tag transforms'); - $list = array(); - foreach ($def->info_tag_transform as $old => $new) { - $new = $this->getClass($new, 'TagTransform_'); - $list[] = "<$old> with $new"; - } - $ret .= $this->element('td', $this->listify($list)); + $ret .= $this->element('th', 'Tag transforms'); + $list = array(); + foreach ($def->info_tag_transform as $old => $new) { + $new = $this->getClass($new, 'TagTransform_'); + $list[] = "<$old> with $new"; + } + $ret .= $this->element('td', $this->listify($list)); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Pre-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre)); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre)); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Post-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post)); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post)); $ret .= $this->end('tr'); $ret .= $this->end('table'); @@ -89,8 +98,10 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Renders the Content Sets table + * @return string */ - protected function renderContentSets() { + protected function renderContentSets() + { $ret = ''; $ret .= $this->start('table'); $ret .= $this->element('caption', 'Content Sets'); @@ -106,8 +117,10 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Renders the Elements ($info) table + * @return string */ - protected function renderInfo() { + protected function renderInfo() + { $ret = ''; $ret .= $this->start('table'); $ret .= $this->element('caption', 'Elements ($info)'); @@ -118,39 +131,39 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer $ret .= $this->end('tr'); foreach ($this->def->info as $name => $def) { $ret .= $this->start('tr'); - $ret .= $this->element('th', "<$name>", array('class'=>'heavy', 'colspan' => 2)); + $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2)); $ret .= $this->end('tr'); $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Inline content'); - $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No'); + $ret .= $this->element('th', 'Inline content'); + $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No'); $ret .= $this->end('tr'); if (!empty($def->excludes)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Excludes'); - $ret .= $this->element('td', $this->listifyTagLookup($def->excludes)); + $ret .= $this->element('th', 'Excludes'); + $ret .= $this->element('td', $this->listifyTagLookup($def->excludes)); $ret .= $this->end('tr'); } if (!empty($def->attr_transform_pre)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Pre-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre)); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre)); $ret .= $this->end('tr'); } if (!empty($def->attr_transform_post)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Post-AttrTransform'); - $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post)); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post)); $ret .= $this->end('tr'); } if (!empty($def->auto_close)) { $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Auto closed by'); - $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close)); + $ret .= $this->element('th', 'Auto closed by'); + $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close)); $ret .= $this->end('tr'); } $ret .= $this->start('tr'); - $ret .= $this->element('th', 'Allowed attributes'); - $ret .= $this->element('td',$this->listifyAttr($def->attr), array(), 0); + $ret .= $this->element('th', 'Allowed attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0); $ret .= $this->end('tr'); if (!empty($def->required_attr)) { @@ -165,64 +178,94 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Renders a row describing the allowed children of an element - * @param $def HTMLPurifier_ChildDef of pertinent element + * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element + * @return string */ - protected function renderChildren($def) { + protected function renderChildren($def) + { $context = new HTMLPurifier_Context(); $ret = ''; $ret .= $this->start('tr'); + $elements = array(); + $attr = array(); + if (isset($def->elements)) { + if ($def->type == 'strictblockquote') { + $def->validateChildren(array(), $this->config, $context); + } + $elements = $def->elements; + } + if ($def->type == 'chameleon') { + $attr['rowspan'] = 2; + } elseif ($def->type == 'empty') { $elements = array(); - $attr = array(); - if (isset($def->elements)) { - if ($def->type == 'strictblockquote') { - $def->validateChildren(array(), $this->config, $context); - } - $elements = $def->elements; - } - if ($def->type == 'chameleon') { - $attr['rowspan'] = 2; - } elseif ($def->type == 'empty') { - $elements = array(); - } elseif ($def->type == 'table') { - $elements = array_flip(array('col', 'caption', 'colgroup', 'thead', - 'tfoot', 'tbody', 'tr')); - } - $ret .= $this->element('th', 'Allowed children', $attr); + } elseif ($def->type == 'table') { + $elements = array_flip( + array( + 'col', + 'caption', + 'colgroup', + 'thead', + 'tfoot', + 'tbody', + 'tr' + ) + ); + } + $ret .= $this->element('th', 'Allowed children', $attr); - if ($def->type == 'chameleon') { + if ($def->type == 'chameleon') { - $ret .= $this->element('td', - '<em>Block</em>: ' . - $this->escape($this->listifyTagLookup($def->block->elements)),0,0); - $ret .= $this->end('tr'); - $ret .= $this->start('tr'); - $ret .= $this->element('td', - '<em>Inline</em>: ' . - $this->escape($this->listifyTagLookup($def->inline->elements)),0,0); + $ret .= $this->element( + 'td', + '<em>Block</em>: ' . + $this->escape($this->listifyTagLookup($def->block->elements)), + null, + 0 + ); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element( + 'td', + '<em>Inline</em>: ' . + $this->escape($this->listifyTagLookup($def->inline->elements)), + null, + 0 + ); - } elseif ($def->type == 'custom') { + } elseif ($def->type == 'custom') { - $ret .= $this->element('td', '<em>'.ucfirst($def->type).'</em>: ' . - $def->dtd_regex); + $ret .= $this->element( + 'td', + '<em>' . ucfirst($def->type) . '</em>: ' . + $def->dtd_regex + ); - } else { - $ret .= $this->element('td', - '<em>'.ucfirst($def->type).'</em>: ' . - $this->escape($this->listifyTagLookup($elements)),0,0); - } + } else { + $ret .= $this->element( + 'td', + '<em>' . ucfirst($def->type) . '</em>: ' . + $this->escape($this->listifyTagLookup($elements)), + null, + 0 + ); + } $ret .= $this->end('tr'); return $ret; } /** * Listifies a tag lookup table. - * @param $array Tag lookup array in form of array('tagname' => true) + * @param array $array Tag lookup array in form of array('tagname' => true) + * @return string */ - protected function listifyTagLookup($array) { + protected function listifyTagLookup($array) + { ksort($array); $list = array(); foreach ($array as $name => $discard) { - if ($name !== '#PCDATA' && !isset($this->def->info[$name])) continue; + if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { + continue; + } $list[] = $name; } return $this->listify($list); @@ -230,13 +273,15 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Listifies a list of objects by retrieving class names and internal state - * @param $array List of objects + * @param array $array List of objects + * @return string * @todo Also add information about internal state */ - protected function listifyObjectList($array) { + protected function listifyObjectList($array) + { ksort($array); $list = array(); - foreach ($array as $discard => $obj) { + foreach ($array as $obj) { $list[] = $this->getClass($obj, 'AttrTransform_'); } return $this->listify($list); @@ -244,13 +289,17 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Listifies a hash of attributes to AttrDef classes - * @param $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @return string */ - protected function listifyAttr($array) { + protected function listifyAttr($array) + { ksort($array); $list = array(); foreach ($array as $name => $obj) { - if ($obj === false) continue; + if ($obj === false) { + continue; + } $list[] = "$name = <i>" . $this->getClass($obj, 'AttrDef_') . '</i>'; } return $this->listify($list); @@ -258,15 +307,18 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer /** * Creates a heavy header row + * @param string $text + * @param int $num + * @return string */ - protected function heavyHeader($text, $num = 1) { + protected function heavyHeader($text, $num = 1) + { $ret = ''; $ret .= $this->start('tr'); $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy')); $ret .= $this->end('tr'); return $ret; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/PropertyList.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php similarity index 50% rename from library/HTMLPurifier/PropertyList.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php index 2b99fb7bc3..189348fd9e 100644 --- a/library/HTMLPurifier/PropertyList.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php @@ -6,61 +6,93 @@ class HTMLPurifier_PropertyList { /** - * Internal data-structure for properties + * Internal data-structure for properties. + * @type array */ protected $data = array(); /** - * Parent plist + * Parent plist. + * @type HTMLPurifier_PropertyList */ protected $parent; + /** + * Cache. + * @type array + */ protected $cache; - public function __construct($parent = null) { + /** + * @param HTMLPurifier_PropertyList $parent Parent plist + */ + public function __construct($parent = null) + { $this->parent = $parent; } /** * Recursively retrieves the value for a key + * @param string $name + * @throws HTMLPurifier_Exception */ - public function get($name) { - if ($this->has($name)) return $this->data[$name]; + public function get($name) + { + if ($this->has($name)) { + return $this->data[$name]; + } // possible performance bottleneck, convert to iterative if necessary - if ($this->parent) return $this->parent->get($name); + if ($this->parent) { + return $this->parent->get($name); + } throw new HTMLPurifier_Exception("Key '$name' not found"); } /** * Sets the value of a key, for this plist + * @param string $name + * @param mixed $value */ - public function set($name, $value) { + public function set($name, $value) + { $this->data[$name] = $value; } /** * Returns true if a given key exists + * @param string $name + * @return bool */ - public function has($name) { + public function has($name) + { return array_key_exists($name, $this->data); } /** * Resets a value to the value of it's parent, usually the default. If * no value is specified, the entire plist is reset. + * @param string $name */ - public function reset($name = null) { - if ($name == null) $this->data = array(); - else unset($this->data[$name]); + public function reset($name = null) + { + if ($name == null) { + $this->data = array(); + } else { + unset($this->data[$name]); + } } /** * Squashes this property list and all of its property lists into a single * array, and returns the array. This value is cached by default. - * @param $force If true, ignores the cache and regenerates the array. + * @param bool $force If true, ignores the cache and regenerates the array. + * @return array */ - public function squash($force = false) { - if ($this->cache !== null && !$force) return $this->cache; + public function squash($force = false) + { + if ($this->cache !== null && !$force) { + return $this->cache; + } if ($this->parent) { return $this->cache = array_merge($this->parent->squash($force), $this->data); } else { @@ -70,15 +102,19 @@ class HTMLPurifier_PropertyList /** * Returns the parent plist. + * @return HTMLPurifier_PropertyList */ - public function getParent() { + public function getParent() + { return $this->parent; } /** * Sets the parent plist. + * @param HTMLPurifier_PropertyList $plist Parent plist */ - public function setParent($plist) { + public function setParent($plist) + { $this->parent = $plist; } } diff --git a/library/HTMLPurifier/PropertyListIterator.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php similarity index 60% rename from library/HTMLPurifier/PropertyListIterator.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php index 8f250443e4..15b330ea30 100644 --- a/library/HTMLPurifier/PropertyListIterator.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php @@ -6,27 +6,37 @@ class HTMLPurifier_PropertyListIterator extends FilterIterator { + /** + * @type int + */ protected $l; + /** + * @type string + */ protected $filter; /** - * @param $data Array of data to iterate over - * @param $filter Optional prefix to only allow values of + * @param Iterator $iterator Array of data to iterate over + * @param string $filter Optional prefix to only allow values of */ - public function __construct(Iterator $iterator, $filter = null) { + public function __construct(Iterator $iterator, $filter = null) + { parent::__construct($iterator); $this->l = strlen($filter); $this->filter = $filter; } - public function accept() { + /** + * @return bool + */ + public function accept() + { $key = $this->getInnerIterator()->key(); - if( strncmp($key, $this->filter, $this->l) !== 0 ) { + if (strncmp($key, $this->filter, $this->l) !== 0) { return false; } return true; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php new file mode 100644 index 0000000000..f58db9042a --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php @@ -0,0 +1,56 @@ +<?php + +/** + * A simple array-backed queue, based off of the classic Okasaki + * persistent amortized queue. The basic idea is to maintain two + * stacks: an input stack and an output stack. When the output + * stack runs out, reverse the input stack and use it as the output + * stack. + * + * We don't use the SPL implementation because it's only supported + * on PHP 5.3 and later. + * + * Exercise: Prove that push/pop on this queue take amortized O(1) time. + * + * Exercise: Extend this queue to be a deque, while preserving amortized + * O(1) time. Some care must be taken on rebalancing to avoid quadratic + * behaviour caused by repeatedly shuffling data from the input stack + * to the output stack and back. + */ +class HTMLPurifier_Queue { + private $input; + private $output; + + public function __construct($input = array()) { + $this->input = $input; + $this->output = array(); + } + + /** + * Shifts an element off the front of the queue. + */ + public function shift() { + if (empty($this->output)) { + $this->output = array_reverse($this->input); + $this->input = array(); + } + if (empty($this->output)) { + return NULL; + } + return array_pop($this->output); + } + + /** + * Pushes an element onto the front of the queue. + */ + public function push($x) { + array_push($this->input, $x); + } + + /** + * Checks if it's empty. + */ + public function isEmpty() { + return empty($this->input) && empty($this->output); + } +} diff --git a/library/HTMLPurifier/Strategy.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php similarity index 66% rename from library/HTMLPurifier/Strategy.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php index 2462865210..e1ff3b72df 100644 --- a/library/HTMLPurifier/Strategy.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php @@ -15,12 +15,12 @@ abstract class HTMLPurifier_Strategy /** * Executes the strategy on the tokens. * - * @param $tokens Array of HTMLPurifier_Token objects to be operated on. - * @param $config Configuration options - * @returns Processed array of token objects. + * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] Processed array of token objects. */ abstract public function execute($tokens, $config, $context); - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Strategy/Composite.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php similarity index 61% rename from library/HTMLPurifier/Strategy/Composite.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php index 816490b799..d7d35ce7d1 100644 --- a/library/HTMLPurifier/Strategy/Composite.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Composite.php @@ -8,18 +8,23 @@ abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy /** * List of strategies to run tokens through. + * @type HTMLPurifier_Strategy[] */ protected $strategies = array(); - abstract public function __construct(); - - public function execute($tokens, $config, $context) { + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { foreach ($this->strategies as $strategy) { $tokens = $strategy->execute($tokens, $config, $context); } return $tokens; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Strategy/Core.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php similarity index 92% rename from library/HTMLPurifier/Strategy/Core.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php index d90e158606..4414c17d6e 100644 --- a/library/HTMLPurifier/Strategy/Core.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php @@ -5,14 +5,13 @@ */ class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite { - - public function __construct() { + public function __construct() + { $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements(); $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed(); $this->strategies[] = new HTMLPurifier_Strategy_FixNesting(); $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes(); } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php new file mode 100644 index 0000000000..6fa673db9a --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php @@ -0,0 +1,181 @@ +<?php + +/** + * Takes a well formed list of tokens and fixes their nesting. + * + * HTML elements dictate which elements are allowed to be their children, + * for example, you can't have a p tag in a span tag. Other elements have + * much more rigorous definitions: tables, for instance, require a specific + * order for their elements. There are also constraints not expressible by + * document type definitions, such as the chameleon nature of ins/del + * tags and global child exclusions. + * + * The first major objective of this strategy is to iterate through all + * the nodes and determine whether or not their children conform to the + * element's definition. If they do not, the child definition may + * optionally supply an amended list of elements that is valid or + * require that the entire node be deleted (and the previous node + * rescanned). + * + * The second objective is to ensure that explicitly excluded elements of + * an element do not appear in its children. Code that accomplishes this + * task is pervasive through the strategy, though the two are distinct tasks + * and could, theoretically, be seperated (although it's not recommended). + * + * @note Whether or not unrecognized children are silently dropped or + * translated into text depends on the child definitions. + * + * @todo Enable nodes to be bubbled out of the structure. This is + * easier with our new algorithm. + */ + +class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy +{ + + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array|HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { + + //####################################################################// + // Pre-processing + + // O(n) pass to convert to a tree, so that we can efficiently + // refer to substrings + $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context); + + // get a copy of the HTML definition + $definition = $config->getHTMLDefinition(); + + $excludes_enabled = !$config->get('Core.DisableExcludes'); + + // setup the context variable 'IsInline', for chameleon processing + // is 'false' when we are not inline, 'true' when it must always + // be inline, and an integer when it is inline for a certain + // branch of the document tree + $is_inline = $definition->info_parent_def->descendants_are_inline; + $context->register('IsInline', $is_inline); + + // setup error collector + $e =& $context->get('ErrorCollector', true); + + //####################################################################// + // Loop initialization + + // stack that contains all elements that are excluded + // it is organized by parent elements, similar to $stack, + // but it is only populated when an element with exclusions is + // processed, i.e. there won't be empty exclusions. + $exclude_stack = array($definition->info_parent_def->excludes); + + // variable that contains the start token while we are processing + // nodes. This enables error reporting to do its job + $node = $top_node; + // dummy token + list($token, $d) = $node->toTokenPair(); + $context->register('CurrentNode', $node); + $context->register('CurrentToken', $token); + + //####################################################################// + // Loop + + // We need to implement a post-order traversal iteratively, to + // avoid running into stack space limits. This is pretty tricky + // to reason about, so we just manually stack-ify the recursive + // variant: + // + // function f($node) { + // foreach ($node->children as $child) { + // f($child); + // } + // validate($node); + // } + // + // Thus, we will represent a stack frame as array($node, + // $is_inline, stack of children) + // e.g. array_reverse($node->children) - already processed + // children. + + $parent_def = $definition->info_parent_def; + $stack = array( + array($top_node, + $parent_def->descendants_are_inline, + $parent_def->excludes, // exclusions + 0) + ); + + while (!empty($stack)) { + list($node, $is_inline, $excludes, $ix) = array_pop($stack); + // recursive call + $go = false; + $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; + while (isset($node->children[$ix])) { + $child = $node->children[$ix++]; + if ($child instanceof HTMLPurifier_Node_Element) { + $go = true; + $stack[] = array($node, $is_inline, $excludes, $ix); + $stack[] = array($child, + // ToDo: I don't think it matters if it's def or + // child_def, but double check this... + $is_inline || $def->descendants_are_inline, + empty($def->excludes) ? $excludes + : array_merge($excludes, $def->excludes), + 0); + break; + } + }; + if ($go) continue; + list($token, $d) = $node->toTokenPair(); + // base case + if ($excludes_enabled && isset($excludes[$node->name])) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); + } else { + // XXX I suppose it would be slightly more efficient to + // avoid the allocation here and have children + // strategies handle it + $children = array(); + foreach ($node->children as $child) { + if (!$child->dead) $children[] = $child; + } + $result = $def->child->validateChildren($children, $config, $context); + if ($result === true) { + // nop + $node->children = $children; + } elseif ($result === false) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); + } else { + $node->children = $result; + if ($e) { + // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators + if (empty($result) && !empty($children)) { + $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); + } else if ($result != $children) { + $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); + } + } + } + } + } + + //####################################################################// + // Post-processing + + // remove context variables + $context->destroy('IsInline'); + $context->destroy('CurrentNode'); + $context->destroy('CurrentToken'); + + //####################################################################// + // Return + + return HTMLPurifier_Arborize::flatten($node, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Strategy/MakeWellFormed.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php similarity index 52% rename from library/HTMLPurifier/Strategy/MakeWellFormed.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php index c736584007..e389e00116 100644 --- a/library/HTMLPurifier/Strategy/MakeWellFormed.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php @@ -2,66 +2,97 @@ /** * Takes tokens makes them well-formed (balance end tags, etc.) + * + * Specification of the armor attributes this strategy uses: + * + * - MakeWellFormed_TagClosedError: This armor field is used to + * suppress tag closed errors for certain tokens [TagClosedSuppress], + * in particular, if a tag was generated automatically by HTML + * Purifier, we may rely on our infrastructure to close it for us + * and shouldn't report an error to the user [TagClosedAuto]. */ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy { /** * Array stream of tokens being processed. + * @type HTMLPurifier_Token[] */ protected $tokens; /** - * Current index in $tokens. + * Current token. + * @type HTMLPurifier_Token */ - protected $t; + protected $token; + + /** + * Zipper managing the true state. + * @type HTMLPurifier_Zipper + */ + protected $zipper; /** * Current nesting of elements. + * @type array */ protected $stack; /** * Injectors active in this stream processing. + * @type HTMLPurifier_Injector[] */ protected $injectors; /** * Current instance of HTMLPurifier_Config. + * @type HTMLPurifier_Config */ protected $config; /** * Current instance of HTMLPurifier_Context. + * @type HTMLPurifier_Context */ protected $context; - public function execute($tokens, $config, $context) { - + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + * @throws HTMLPurifier_Exception + */ + public function execute($tokens, $config, $context) + { $definition = $config->getHTMLDefinition(); // local variables $generator = new HTMLPurifier_Generator($config, $context); $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + // used for autoclose early abortion + $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config); $e = $context->get('ErrorCollector', true); - $t = false; // token index $i = false; // injector index - $token = false; // the current token - $reprocess = false; // whether or not to reprocess the same token + list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens); + if ($token === NULL) { + return array(); + } + $reprocess = false; // whether or not to reprocess the same token $stack = array(); // member variables - $this->stack =& $stack; - $this->t =& $t; - $this->tokens =& $tokens; - $this->config = $config; + $this->stack =& $stack; + $this->tokens =& $tokens; + $this->token =& $token; + $this->zipper =& $zipper; + $this->config = $config; $this->context = $context; // context variables $context->register('CurrentNesting', $stack); - $context->register('InputIndex', $t); - $context->register('InputTokens', $tokens); - $context->register('CurrentToken', $token); + $context->register('InputZipper', $zipper); + $context->register('CurrentToken', $token); // -- begin INJECTOR -- @@ -73,9 +104,13 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy unset($injectors['Custom']); // special case foreach ($injectors as $injector => $b) { // XXX: Fix with a legitimate lookup table of enabled filters - if (strpos($injector, '.') !== false) continue; + if (strpos($injector, '.') !== false) { + continue; + } $injector = "HTMLPurifier_Injector_$injector"; - if (!$b) continue; + if (!$b) { + continue; + } $this->injectors[] = new $injector; } foreach ($def_injectors as $injector) { @@ -83,7 +118,9 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy $this->injectors[] = $injector; } foreach ($custom_injectors as $injector) { - if (!$injector) continue; + if (!$injector) { + continue; + } if (is_string($injector)) { $injector = "HTMLPurifier_Injector_$injector"; $injector = new $injector; @@ -95,14 +132,16 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy // variables for performance reasons foreach ($this->injectors as $ix => $injector) { $error = $injector->prepare($config, $context); - if (!$error) continue; + if (!$error) { + continue; + } array_splice($this->injectors, $ix, 1); // rm the injector trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING); } // -- end INJECTOR -- - // a note on punting: + // a note on reprocessing: // In order to reduce code duplication, whenever some code needs // to make HTML changes in order to make things "correct", the // new HTML gets sent through the purifier, regardless of its @@ -111,70 +150,75 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy // punt ($reprocess = true; continue;) and it does that for us. // isset is in loop because $tokens size changes during loop exec - for ( - $t = 0; - $t == 0 || isset($tokens[$t - 1]); - // only increment if we don't need to reprocess - $reprocess ? $reprocess = false : $t++ - ) { + for (;; + // only increment if we don't need to reprocess + $reprocess ? $reprocess = false : $token = $zipper->next($token)) { // check for a rewind - if (is_int($i) && $i >= 0) { + if (is_int($i)) { // possibility: disable rewinding if the current token has a // rewind set on it already. This would offer protection from // infinite loop, but might hinder some advanced rewinding. - $rewind_to = $this->injectors[$i]->getRewind(); - if (is_int($rewind_to) && $rewind_to < $t) { - if ($rewind_to < 0) $rewind_to = 0; - while ($t > $rewind_to) { - $t--; - $prev = $tokens[$t]; + $rewind_offset = $this->injectors[$i]->getRewindOffset(); + if (is_int($rewind_offset)) { + for ($j = 0; $j < $rewind_offset; $j++) { + if (empty($zipper->front)) break; + $token = $zipper->prev($token); // indicate that other injectors should not process this token, // but we need to reprocess it - unset($prev->skip[$i]); - $prev->rewind = $i; - if ($prev instanceof HTMLPurifier_Token_Start) array_pop($this->stack); - elseif ($prev instanceof HTMLPurifier_Token_End) $this->stack[] = $prev->start; + unset($token->skip[$i]); + $token->rewind = $i; + if ($token instanceof HTMLPurifier_Token_Start) { + array_pop($this->stack); + } elseif ($token instanceof HTMLPurifier_Token_End) { + $this->stack[] = $token->start; + } } } $i = false; } // handle case of document end - if (!isset($tokens[$t])) { + if ($token === NULL) { // kill processing if stack is empty - if (empty($this->stack)) break; + if (empty($this->stack)) { + break; + } // peek $top_nesting = array_pop($this->stack); $this->stack[] = $top_nesting; - // send error + // send error [TagClosedSuppress] if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); } // append, don't splice, since this is the end - $tokens[] = new HTMLPurifier_Token_End($top_nesting->name); + $token = new HTMLPurifier_Token_End($top_nesting->name); // punt! $reprocess = true; continue; } - $token = $tokens[$t]; - - //echo '<br>'; printTokens($tokens, $t); printTokens($this->stack); + //echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack); //flush(); // quick-check: if it's not a tag, no need to process if (empty($token->is_tag)) { if ($token instanceof HTMLPurifier_Token_Text) { foreach ($this->injectors as $i => $injector) { - if (isset($token->skip[$i])) continue; - if ($token->rewind !== null && $token->rewind !== $i) continue; - $injector->handleText($token); - $this->processToken($token, $i); + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + // XXX fuckup + $r = $token; + $injector->handleText($r); + $token = $this->processToken($r, $i); $reprocess = true; break; } @@ -193,12 +237,22 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy $ok = false; if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) { // claims to be a start tag but is empty - $token = new HTMLPurifier_Token_Empty($token->name, $token->attr); + $token = new HTMLPurifier_Token_Empty( + $token->name, + $token->attr, + $token->line, + $token->col, + $token->armor + ); $ok = true; } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { // claims to be empty but really is a start tag - $this->swap(new HTMLPurifier_Token_End($token->name)); - $this->insertBefore(new HTMLPurifier_Token_Start($token->name, $token->attr)); + // NB: this assignment is required + $old_token = $token; + $token = new HTMLPurifier_Token_End($token->name); + $token = $this->insertBefore( + new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor) + ); // punt (since we had to modify the input stream in a non-trivial way) $reprocess = true; continue; @@ -211,56 +265,97 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy // ...unless they also have to close their parent if (!empty($this->stack)) { + // Performance note: you might think that it's rather + // inefficient, recalculating the autoclose information + // for every tag that a token closes (since when we + // do an autoclose, we push a new token into the + // stream and then /process/ that, before + // re-processing this token.) But this is + // necessary, because an injector can make an + // arbitrary transformations to the autoclosing + // tokens we introduce, so things may have changed + // in the meantime. Also, doing the inefficient thing is + // "easy" to reason about (for certain perverse definitions + // of "easy") + $parent = array_pop($this->stack); $this->stack[] = $parent; + $parent_def = null; + $parent_elements = null; + $autoclose = false; if (isset($definition->info[$parent->name])) { - $elements = $definition->info[$parent->name]->child->getAllowedElements($config); - $autoclose = !isset($elements[$token->name]); - } else { - $autoclose = false; + $parent_def = $definition->info[$parent->name]; + $parent_elements = $parent_def->child->getAllowedElements($config); + $autoclose = !isset($parent_elements[$token->name]); } if ($autoclose && $definition->info[$token->name]->wrap) { - // Check if an element can be wrapped by another - // element to make it valid in a context (for + // Check if an element can be wrapped by another + // element to make it valid in a context (for // example, <ul><ul> needs a <li> in between) $wrapname = $definition->info[$token->name]->wrap; $wrapdef = $definition->info[$wrapname]; $elements = $wrapdef->child->getAllowedElements($config); - $parent_elements = $definition->info[$parent->name]->child->getAllowedElements($config); if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { $newtoken = new HTMLPurifier_Token_Start($wrapname); - $this->insertBefore($newtoken); + $token = $this->insertBefore($newtoken); $reprocess = true; continue; } } $carryover = false; - if ($autoclose && $definition->info[$parent->name]->formatting) { + if ($autoclose && $parent_def->formatting) { $carryover = true; } if ($autoclose) { - // errors need to be updated - $new_token = new HTMLPurifier_Token_End($parent->name); - $new_token->start = $parent; - if ($carryover) { - $element = clone $parent; - $element->armor['MakeWellFormed_TagClosedError'] = true; - $element->carryover = true; - $this->processToken(array($new_token, $token, $element)); - } else { - $this->insertBefore($new_token); - } - if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { - if (!$carryover) { - $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); - } else { - $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent); + // check if this autoclose is doomed to fail + // (this rechecks $parent, which his harmless) + $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); + if (!$autoclose_ok) { + foreach ($this->stack as $ancestor) { + $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); + if (isset($elements[$token->name])) { + $autoclose_ok = true; + break; + } + if ($definition->info[$token->name]->wrap) { + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $wrap_elements = $wrapdef->child->getAllowedElements($config); + if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { + $autoclose_ok = true; + break; + } + } } } + if ($autoclose_ok) { + // errors need to be updated + $new_token = new HTMLPurifier_Token_End($parent->name); + $new_token->start = $parent; + // [TagClosedSuppress] + if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { + if (!$carryover) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); + } else { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent); + } + } + if ($carryover) { + $element = clone $parent; + // [TagClosedAuto] + $element->armor['MakeWellFormed_TagClosedError'] = true; + $element->carryover = true; + $token = $this->processToken(array($new_token, $token, $element)); + } else { + $token = $this->insertBefore($new_token); + } + } else { + $token = $this->remove(); + } $reprocess = true; continue; } @@ -271,20 +366,26 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy if ($ok) { foreach ($this->injectors as $i => $injector) { - if (isset($token->skip[$i])) continue; - if ($token->rewind !== null && $token->rewind !== $i) continue; - $injector->handleElement($token); - $this->processToken($token, $i); + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleElement($r); + $token = $this->processToken($r, $i); $reprocess = true; break; } if (!$reprocess) { // ah, nothing interesting happened; do normal processing - $this->swap($token); if ($token instanceof HTMLPurifier_Token_Start) { $this->stack[] = $token; } elseif ($token instanceof HTMLPurifier_Token_End) { - throw new HTMLPurifier_Exception('Improper handling of end tag in start code; possible error in MakeWellFormed'); + throw new HTMLPurifier_Exception( + 'Improper handling of end tag in start code; possible error in MakeWellFormed' + ); } } continue; @@ -298,13 +399,15 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy // make sure that we have something open if (empty($this->stack)) { if ($escape_invalid_tags) { - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); - $this->swap(new HTMLPurifier_Token_Text( - $generator->generateFromToken($token) - )); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); } else { - $this->remove(); - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + } + $token = $this->remove(); } $reprocess = true; continue; @@ -318,10 +421,15 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy if ($current_parent->name == $token->name) { $token->start = $current_parent; foreach ($this->injectors as $i => $injector) { - if (isset($token->skip[$i])) continue; - if ($token->rewind !== null && $token->rewind !== $i) continue; - $injector->handleEnd($token); - $this->processToken($token, $i); + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleEnd($r); + $token = $this->processToken($r, $i); $this->stack[] = $current_parent; $reprocess = true; break; @@ -349,13 +457,15 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy // we didn't find the tag, so remove if ($skipped_tags === false) { if ($escape_invalid_tags) { - $this->swap(new HTMLPurifier_Token_Text( - $generator->generateFromToken($token) - )); - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); } else { - $this->remove(); - if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + } + $token = $this->remove(); } $reprocess = true; continue; @@ -366,7 +476,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy if ($e) { for ($j = $c - 1; $j > 0; $j--) { // notice we exclude $j == 0, i.e. the current ending tag, from - // the errors... + // the errors... [TagClosedSuppress] if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); } @@ -381,24 +491,24 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy $new_token->start = $skipped_tags[$j]; array_unshift($replace, $new_token); if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { + // [TagClosedAuto] $element = clone $skipped_tags[$j]; $element->carryover = true; $element->armor['MakeWellFormed_TagClosedError'] = true; $replace[] = $element; } } - $this->processToken($replace); + $token = $this->processToken($replace); $reprocess = true; continue; } - $context->destroy('CurrentNesting'); - $context->destroy('InputTokens'); - $context->destroy('InputIndex'); $context->destroy('CurrentToken'); + $context->destroy('CurrentNesting'); + $context->destroy('InputZipper'); - unset($this->injectors, $this->stack, $this->tokens, $this->t); - return $tokens; + unset($this->injectors, $this->stack, $this->tokens); + return $zipper->toArray($token); } /** @@ -417,25 +527,38 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy * If $token is an integer, that number of tokens (with the first token * being the current one) will be deleted. * - * @param $token Token substitution value - * @param $injector Injector that performed the substitution; default is if + * @param HTMLPurifier_Token|array|int|bool $token Token substitution value + * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if * this is not an injector related operation. + * @throws HTMLPurifier_Exception */ - protected function processToken($token, $injector = -1) { - + protected function processToken($token, $injector = -1) + { // normalize forms of token - if (is_object($token)) $token = array(1, $token); - if (is_int($token)) $token = array($token); - if ($token === false) $token = array(1); - if (!is_array($token)) throw new HTMLPurifier_Exception('Invalid token type from injector'); - if (!is_int($token[0])) array_unshift($token, 1); - if ($token[0] === 0) throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); + if (is_object($token)) { + $token = array(1, $token); + } + if (is_int($token)) { + $token = array($token); + } + if ($token === false) { + $token = array(1); + } + if (!is_array($token)) { + throw new HTMLPurifier_Exception('Invalid token type from injector'); + } + if (!is_int($token[0])) { + array_unshift($token, 1); + } + if ($token[0] === 0) { + throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); + } // $token is now an array with the following form: // array(number nodes to delete, new node 1, new node 2, ...) $delete = array_shift($token); - $old = array_splice($this->tokens, $this->t, $delete, $token); + list($old, $r) = $this->zipper->splice($this->token, $delete, $token); if ($injector > -1) { // determine appropriate skips @@ -446,30 +569,32 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy } } + return $r; + } /** - * Inserts a token before the current token. Cursor now points to this token + * Inserts a token before the current token. Cursor now points to + * this token. You must reprocess after this. + * @param HTMLPurifier_Token $token */ - private function insertBefore($token) { - array_splice($this->tokens, $this->t, 0, array($token)); + private function insertBefore($token) + { + // NB not $this->zipper->insertBefore(), due to positioning + // differences + $splice = $this->zipper->splice($this->token, 0, array($token)); + + return $splice[1]; } /** * Removes current token. Cursor now points to new token occupying previously - * occupied space. + * occupied space. You must reprocess after this. */ - private function remove() { - array_splice($this->tokens, $this->t, 1); + private function remove() + { + return $this->zipper->delete(); } - - /** - * Swap current token with new token. Cursor points to new token (no change). - */ - private function swap($token) { - $this->tokens[$this->t] = $token; - } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php similarity index 64% rename from library/HTMLPurifier/Strategy/RemoveForeignElements.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php index cf3a33e406..1a8149eccb 100644 --- a/library/HTMLPurifier/Strategy/RemoveForeignElements.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php @@ -11,19 +11,29 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy { - public function execute($tokens, $config, $context) { + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array|HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { $definition = $config->getHTMLDefinition(); $generator = new HTMLPurifier_Generator($config, $context); $result = array(); $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); - $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); + $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); // currently only used to determine if comments should be kept $trusted = $config->get('HTML.Trusted'); + $comment_lookup = $config->get('HTML.AllowedComments'); + $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); + $check_comments = $comment_lookup !== array() || $comment_regexp !== null; $remove_script_contents = $config->get('Core.RemoveScriptContents'); - $hidden_elements = $config->get('Core.HiddenElements'); + $hidden_elements = $config->get('Core.HiddenElements'); // remove script contents compatibility if ($remove_script_contents === true) { @@ -48,34 +58,31 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy $e =& $context->get('ErrorCollector'); } - foreach($tokens as $token) { + foreach ($tokens as $token) { if ($remove_until) { if (empty($token->is_tag) || $token->name !== $remove_until) { continue; } } - if (!empty( $token->is_tag )) { + if (!empty($token->is_tag)) { // DEFINITION CALL // before any processing, try to transform the element - if ( - isset($definition->info_tag_transform[$token->name]) - ) { + if (isset($definition->info_tag_transform[$token->name])) { $original_name = $token->name; // there is a transformation for this tag // DEFINITION CALL $token = $definition-> - info_tag_transform[$token->name]-> - transform($token, $config, $context); - if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); + info_tag_transform[$token->name]->transform($token, $config, $context); + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); + } } if (isset($definition->info[$token->name])) { - // mostly everything's good, but // we need to make sure required attributes are in order - if ( - ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && + if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && $definition->info[$token->name]->required_attr && ($token->name != 'img' || $remove_invalid_img) // ensure config option still works ) { @@ -88,7 +95,13 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy } } if (!$ok) { - if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', $name); + if ($e) { + $e->send( + E_ERROR, + 'Strategy_RemoveForeignElements: Missing required attribute', + $name + ); + } continue; } $token->armor['ValidateAttributes'] = true; @@ -102,7 +115,9 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy } elseif ($escape_invalid_tags) { // invalid tag, generate HTML representation and insert in - if ($e) $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + if ($e) { + $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + } $token = new HTMLPurifier_Token_Text( $generator->generateFromToken($token) ); @@ -117,9 +132,13 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy } else { $remove_until = false; } - if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + } } else { - if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + } } continue; } @@ -128,26 +147,46 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy if ($textify_comments !== false) { $data = $token->data; $token = new HTMLPurifier_Token_Text($data); - } elseif ($trusted) { - // keep, but perform comment cleaning + } elseif ($trusted || $check_comments) { + // always cleanup comments + $trailing_hyphen = false; if ($e) { // perform check whether or not there's a trailing hyphen if (substr($token->data, -1) == '-') { - $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'); + $trailing_hyphen = true; } } $token->data = rtrim($token->data, '-'); $found_double_hyphen = false; while (strpos($token->data, '--') !== false) { - if ($e && !$found_double_hyphen) { - $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); - } - $found_double_hyphen = true; // prevent double-erroring + $found_double_hyphen = true; $token->data = str_replace('--', '-', $token->data); } + if ($trusted || !empty($comment_lookup[trim($token->data)]) || + ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) { + // OK good + if ($e) { + if ($trailing_hyphen) { + $e->send( + E_NOTICE, + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' + ); + } + if ($found_double_hyphen) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); + } + } + } else { + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } } else { // strip comments - if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } continue; } } elseif ($token instanceof HTMLPurifier_Token_Text) { @@ -160,12 +199,9 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy // we removed tokens until the end, throw error $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until); } - $context->destroy('CurrentToken'); - return $result; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Strategy/ValidateAttributes.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php similarity index 65% rename from library/HTMLPurifier/Strategy/ValidateAttributes.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php index c3328a9d44..fbb3d27c81 100644 --- a/library/HTMLPurifier/Strategy/ValidateAttributes.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php @@ -7,8 +7,14 @@ class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy { - public function execute($tokens, $config, $context) { - + /** + * @param HTMLPurifier_Token[] $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function execute($tokens, $config, $context) + { // setup validator $validator = new HTMLPurifier_AttrValidator(); @@ -19,21 +25,21 @@ class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy // only process tokens that have attributes, // namely start and empty tags - if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) continue; + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { + continue; + } // skip tokens that are armored - if (!empty($token->armor['ValidateAttributes'])) continue; + if (!empty($token->armor['ValidateAttributes'])) { + continue; + } // note that we have no facilities here for removing tokens $validator->validateToken($token, $config, $context); - - $tokens[$key] = $token; // for PHP 4 } $context->destroy('CurrentToken'); - return $tokens; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/StringHash.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php similarity index 75% rename from library/HTMLPurifier/StringHash.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php index 62085c5c2f..c07370197a 100644 --- a/library/HTMLPurifier/StringHash.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php @@ -10,28 +10,36 @@ */ class HTMLPurifier_StringHash extends ArrayObject { + /** + * @type array + */ protected $accessed = array(); /** * Retrieves a value, and logs the access. + * @param mixed $index + * @return mixed */ - public function offsetGet($index) { + public function offsetGet($index) + { $this->accessed[$index] = true; return parent::offsetGet($index); } /** * Returns a lookup array of all array indexes that have been accessed. - * @return Array in form array($index => true). + * @return array in form array($index => true). */ - public function getAccessed() { + public function getAccessed() + { return $this->accessed; } /** * Resets the access array. */ - public function resetAccessed() { + public function resetAccessed() + { $this->accessed = array(); } } diff --git a/library/HTMLPurifier/StringHashParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php similarity index 73% rename from library/HTMLPurifier/StringHashParser.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php index f3e70c712f..7c73f80835 100644 --- a/library/HTMLPurifier/StringHashParser.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php @@ -28,15 +28,25 @@ class HTMLPurifier_StringHashParser { + /** + * @type string + */ public $default = 'ID'; /** * Parses a file that contains a single string-hash. + * @param string $file + * @return array */ - public function parseFile($file) { - if (!file_exists($file)) return false; + public function parseFile($file) + { + if (!file_exists($file)) { + return false; + } $fh = fopen($file, 'r'); - if (!$fh) return false; + if (!$fh) { + return false; + } $ret = $this->parseHandle($fh); fclose($fh); return $ret; @@ -44,12 +54,19 @@ class HTMLPurifier_StringHashParser /** * Parses a file that contains multiple string-hashes delimited by '----' + * @param string $file + * @return array */ - public function parseMultiFile($file) { - if (!file_exists($file)) return false; + public function parseMultiFile($file) + { + if (!file_exists($file)) { + return false; + } $ret = array(); $fh = fopen($file, 'r'); - if (!$fh) return false; + if (!$fh) { + return false; + } while (!feof($fh)) { $ret[] = $this->parseHandle($fh); } @@ -62,26 +79,36 @@ class HTMLPurifier_StringHashParser * @note While it's possible to simulate in-memory parsing by using * custom stream wrappers, if such a use-case arises we should * factor out the file handle into its own class. - * @param $fh File handle with pointer at start of valid string-hash + * @param resource $fh File handle with pointer at start of valid string-hash * block. + * @return array */ - protected function parseHandle($fh) { + protected function parseHandle($fh) + { $state = false; $single = false; $ret = array(); do { $line = fgets($fh); - if ($line === false) break; + if ($line === false) { + break; + } $line = rtrim($line, "\n\r"); - if (!$state && $line === '') continue; - if ($line === '----') break; + if (!$state && $line === '') { + continue; + } + if ($line === '----') { + break; + } if (strncmp('--#', $line, 3) === 0) { // Comment continue; } elseif (strncmp('--', $line, 2) === 0) { // Multiline declaration $state = trim($line, '- '); - if (!isset($ret[$state])) $ret[$state] = ''; + if (!isset($ret[$state])) { + $ret[$state] = ''; + } continue; } elseif (!$state) { $single = true; @@ -104,7 +131,6 @@ class HTMLPurifier_StringHashParser } while (!feof($fh)); return $ret; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/TagTransform.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php similarity index 62% rename from library/HTMLPurifier/TagTransform.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php index 210a447217..7b8d833433 100644 --- a/library/HTMLPurifier/TagTransform.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php @@ -8,14 +8,15 @@ abstract class HTMLPurifier_TagTransform /** * Tag name to transform the tag to. + * @type string */ public $transform_to; /** * Transforms the obsolete tag into the valid tag. - * @param $tag Tag to be transformed. - * @param $config Mandatory HTMLPurifier_Config object - * @param $context Mandatory HTMLPurifier_Context object + * @param HTMLPurifier_Token_Tag $tag Tag to be transformed. + * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object + * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object */ abstract public function transform($tag, $config, $context); @@ -23,14 +24,14 @@ abstract class HTMLPurifier_TagTransform * Prepends CSS properties to the style attribute, creating the * attribute if it doesn't exist. * @warning Copied over from AttrTransform, be sure to keep in sync - * @param $attr Attribute array to process (passed by reference) - * @param $css CSS to prepend + * @param array $attr Attribute array to process (passed by reference) + * @param string $css CSS to prepend */ - protected function prependCSS(&$attr, $css) { + protected function prependCSS(&$attr, $css) + { $attr['style'] = isset($attr['style']) ? $attr['style'] : ''; $attr['style'] = $css . $attr['style']; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/TagTransform/Font.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php similarity index 71% rename from library/HTMLPurifier/TagTransform/Font.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php index ed2463786d..7853d90bc6 100644 --- a/library/HTMLPurifier/TagTransform/Font.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Font.php @@ -17,9 +17,14 @@ */ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform { - + /** + * @type string + */ public $transform_to = 'span'; + /** + * @type array + */ protected $_size_lookup = array( '0' => 'xx-small', '1' => 'xx-small', @@ -37,8 +42,14 @@ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform '+4' => '300%' ); - public function transform($tag, $config, $context) { - + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token_End|string + */ + public function transform($tag, $config, $context) + { if ($tag instanceof HTMLPurifier_Token_End) { $new_tag = clone $tag; $new_tag->name = $this->transform_to; @@ -63,17 +74,25 @@ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform // handle size transform if (isset($attr['size'])) { // normalize large numbers - if ($attr['size']{0} == '+' || $attr['size']{0} == '-') { - $size = (int) $attr['size']; - if ($size < -2) $attr['size'] = '-2'; - if ($size > 4) $attr['size'] = '+4'; - } else { - $size = (int) $attr['size']; - if ($size > 7) $attr['size'] = '7'; + if ($attr['size'] !== '') { + if ($attr['size']{0} == '+' || $attr['size']{0} == '-') { + $size = (int)$attr['size']; + if ($size < -2) { + $attr['size'] = '-2'; + } + if ($size > 4) { + $attr['size'] = '+4'; + } + } else { + $size = (int)$attr['size']; + if ($size > 7) { + $attr['size'] = '7'; + } + } } if (isset($this->_size_lookup[$attr['size']])) { $prepend_style .= 'font-size:' . - $this->_size_lookup[$attr['size']] . ';'; + $this->_size_lookup[$attr['size']] . ';'; } unset($attr['size']); } @@ -89,7 +108,6 @@ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform $new_tag->attr = $attr; return $new_tag; - } } diff --git a/library/HTMLPurifier/TagTransform/Simple.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php similarity index 61% rename from library/HTMLPurifier/TagTransform/Simple.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php index 0e36130f25..71bf10b91f 100644 --- a/library/HTMLPurifier/TagTransform/Simple.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php @@ -7,19 +7,29 @@ */ class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform { - + /** + * @type string + */ protected $style; /** - * @param $transform_to Tag name to transform to. - * @param $style CSS style to add to the tag + * @param string $transform_to Tag name to transform to. + * @param string $style CSS style to add to the tag */ - public function __construct($transform_to, $style = null) { + public function __construct($transform_to, $style = null) + { $this->transform_to = $transform_to; $this->style = $style; } - public function transform($tag, $config, $context) { + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function transform($tag, $config, $context) + { $new_tag = clone $tag; $new_tag->name = $this->transform_to; if (!is_null($this->style) && @@ -29,7 +39,6 @@ class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform } return $new_tag; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token.php new file mode 100644 index 0000000000..85b85e072d --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token.php @@ -0,0 +1,100 @@ +<?php + +/** + * Abstract base token class that all others inherit from. + */ +abstract class HTMLPurifier_Token +{ + /** + * Line number node was on in source document. Null if unknown. + * @type int + */ + public $line; + + /** + * Column of line node was on in source document. Null if unknown. + * @type int + */ + public $col; + + /** + * Lookup array of processing that this token is exempt from. + * Currently, valid values are "ValidateAttributes" and + * "MakeWellFormed_TagClosedError" + * @type array + */ + public $armor = array(); + + /** + * Used during MakeWellFormed. + * @type + */ + public $skip; + + /** + * @type + */ + public $rewind; + + /** + * @type + */ + public $carryover; + + /** + * @param string $n + * @return null|string + */ + public function __get($n) + { + if ($n === 'type') { + trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE); + switch (get_class($this)) { + case 'HTMLPurifier_Token_Start': + return 'start'; + case 'HTMLPurifier_Token_Empty': + return 'empty'; + case 'HTMLPurifier_Token_End': + return 'end'; + case 'HTMLPurifier_Token_Text': + return 'text'; + case 'HTMLPurifier_Token_Comment': + return 'comment'; + default: + return null; + } + } + } + + /** + * Sets the position of the token in the source document. + * @param int $l + * @param int $c + */ + public function position($l = null, $c = null) + { + $this->line = $l; + $this->col = $c; + } + + /** + * Convenience function for DirectLex settings line/col position. + * @param int $l + * @param int $c + */ + public function rawPosition($l, $c) + { + if ($c === -1) { + $l++; + } + $this->line = $l; + $this->col = $c; + } + + /** + * Converts a token into its corresponding node. + */ + abstract public function toNode(); +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php new file mode 100644 index 0000000000..23453c7052 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php @@ -0,0 +1,38 @@ +<?php + +/** + * Concrete comment token class. Generally will be ignored. + */ +class HTMLPurifier_Token_Comment extends HTMLPurifier_Token +{ + /** + * Character data within comment. + * @type string + */ + public $data; + + /** + * @type bool + */ + public $is_whitespace = true; + + /** + * Transparent constructor. + * + * @param string $data String comment data. + * @param int $line + * @param int $col + */ + public function __construct($data, $line = null, $col = null) + { + $this->data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Token/Empty.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php similarity index 54% rename from library/HTMLPurifier/Token/Empty.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php index 2a82b47ad1..78a95f5555 100644 --- a/library/HTMLPurifier/Token/Empty.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php @@ -5,7 +5,11 @@ */ class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag { - + public function toNode() { + $n = parent::toNode(); + $n->empty = true; + return $n; + } } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Token/End.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php similarity index 58% rename from library/HTMLPurifier/Token/End.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php index 353e79daf7..59b38fdc57 100644 --- a/library/HTMLPurifier/Token/End.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php @@ -10,10 +10,15 @@ class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag { /** - * Token that started this node. Added by MakeWellFormed. Please - * do not edit this! + * Token that started this node. + * Added by MakeWellFormed. Please do not edit this! + * @type HTMLPurifier_Token */ public $start; + + public function toNode() { + throw new Exception("HTMLPurifier_Token_End->toNode not supported!"); + } } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Token/Start.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php similarity index 99% rename from library/HTMLPurifier/Token/Start.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php index e0e14fc624..019f317ada 100644 --- a/library/HTMLPurifier/Token/Start.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php @@ -5,7 +5,6 @@ */ class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag { - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/Token/Tag.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php similarity index 72% rename from library/HTMLPurifier/Token/Tag.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php index 798be028ee..d643fa64e2 100644 --- a/library/HTMLPurifier/Token/Tag.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Tag.php @@ -3,13 +3,14 @@ /** * Abstract class of a tag token (start, end or empty), and its behavior. */ -class HTMLPurifier_Token_Tag extends HTMLPurifier_Token +abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token { /** * Static bool marker that indicates the class is a tag. * * This allows us to check objects with <tt>!empty($obj->is_tag)</tt> * without having to use a function call <tt>is_a()</tt>. + * @type bool */ public $is_tag = true; @@ -19,21 +20,27 @@ class HTMLPurifier_Token_Tag extends HTMLPurifier_Token * @note Strictly speaking, XML tags are case sensitive, so we shouldn't * be lower-casing them, but these tokens cater to HTML tags, which are * insensitive. + * @type string */ public $name; /** * Associative array of the tag's attributes. + * @type array */ public $attr = array(); /** * Non-overloaded constructor, which lower-cases passed tag name. * - * @param $name String name. - * @param $attr Associative array of attributes. + * @param string $name String name. + * @param array $attr Associative array of attributes. + * @param int $line + * @param int $col + * @param array $armor */ - public function __construct($name, $attr = array(), $line = null, $col = null) { + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) + { $this->name = ctype_lower($name) ? $name : strtolower($name); foreach ($attr as $key => $value) { // normalization only necessary when key is not lowercase @@ -49,7 +56,12 @@ class HTMLPurifier_Token_Tag extends HTMLPurifier_Token } $this->attr = $attr; $this->line = $line; - $this->col = $col; + $this->col = $col; + $this->armor = $armor; + } + + public function toNode() { + return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); } } diff --git a/library/HTMLPurifier/Token/Text.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php similarity index 54% rename from library/HTMLPurifier/Token/Text.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php index 82efd823d6..f26a1c211b 100644 --- a/library/HTMLPurifier/Token/Text.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php @@ -12,22 +12,42 @@ class HTMLPurifier_Token_Text extends HTMLPurifier_Token { - public $name = '#PCDATA'; /**< PCDATA tag name compatible with DTD. */ - public $data; /**< Parsed character data of text. */ - public $is_whitespace; /**< Bool indicating if node is whitespace. */ + /** + * @type string + */ + public $name = '#PCDATA'; + /**< PCDATA tag name compatible with DTD. */ + + /** + * @type string + */ + public $data; + /**< Parsed character data of text. */ + + /** + * @type bool + */ + public $is_whitespace; + + /**< Bool indicating if node is whitespace. */ /** * Constructor, accepts data and determines if it is whitespace. - * - * @param $data String parsed character data. + * @param string $data String parsed character data. + * @param int $line + * @param int $col */ - public function __construct($data, $line = null, $col = null) { + public function __construct($data, $line = null, $col = null) + { $this->data = $data; $this->is_whitespace = ctype_space($data); $this->line = $line; - $this->col = $col; + $this->col = $col; } + public function toNode() { + return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); + } } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php new file mode 100644 index 0000000000..dea2446b93 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php @@ -0,0 +1,118 @@ +<?php + +/** + * Factory for token generation. + * + * @note Doing some benchmarking indicates that the new operator is much + * slower than the clone operator (even discounting the cost of the + * constructor). This class is for that optimization. + * Other then that, there's not much point as we don't + * maintain parallel HTMLPurifier_Token hierarchies (the main reason why + * you'd want to use an abstract factory). + * @todo Port DirectLex to use this + */ +class HTMLPurifier_TokenFactory +{ + // p stands for prototype + + /** + * @type HTMLPurifier_Token_Start + */ + private $p_start; + + /** + * @type HTMLPurifier_Token_End + */ + private $p_end; + + /** + * @type HTMLPurifier_Token_Empty + */ + private $p_empty; + + /** + * @type HTMLPurifier_Token_Text + */ + private $p_text; + + /** + * @type HTMLPurifier_Token_Comment + */ + private $p_comment; + + /** + * Generates blank prototypes for cloning. + */ + public function __construct() + { + $this->p_start = new HTMLPurifier_Token_Start('', array()); + $this->p_end = new HTMLPurifier_Token_End(''); + $this->p_empty = new HTMLPurifier_Token_Empty('', array()); + $this->p_text = new HTMLPurifier_Token_Text(''); + $this->p_comment = new HTMLPurifier_Token_Comment(''); + } + + /** + * Creates a HTMLPurifier_Token_Start. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start + */ + public function createStart($name, $attr = array()) + { + $p = clone $this->p_start; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_End. + * @param string $name Tag name + * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End + */ + public function createEnd($name) + { + $p = clone $this->p_end; + $p->__construct($name); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Empty. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty + */ + public function createEmpty($name, $attr = array()) + { + $p = clone $this->p_empty; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Text. + * @param string $data Data of text token + * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text + */ + public function createText($data) + { + $p = clone $this->p_text; + $p->__construct($data); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Comment. + * @param string $data Data of comment token + * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment + */ + public function createComment($data) + { + $p = clone $this->p_comment; + $p->__construct($data); + return $p; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php new file mode 100644 index 0000000000..a5e7ae2984 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URI.php @@ -0,0 +1,314 @@ +<?php + +/** + * HTML Purifier's internal representation of a URI. + * @note + * Internal data-structures are completely escaped. If the data needs + * to be used in a non-URI context (which is very unlikely), be sure + * to decode it first. The URI may not necessarily be well-formed until + * validate() is called. + */ +class HTMLPurifier_URI +{ + /** + * @type string + */ + public $scheme; + + /** + * @type string + */ + public $userinfo; + + /** + * @type string + */ + public $host; + + /** + * @type int + */ + public $port; + + /** + * @type string + */ + public $path; + + /** + * @type string + */ + public $query; + + /** + * @type string + */ + public $fragment; + + /** + * @param string $scheme + * @param string $userinfo + * @param string $host + * @param int $port + * @param string $path + * @param string $query + * @param string $fragment + * @note Automatically normalizes scheme and port + */ + public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment) + { + $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme); + $this->userinfo = $userinfo; + $this->host = $host; + $this->port = is_null($port) ? $port : (int)$port; + $this->path = $path; + $this->query = $query; + $this->fragment = $fragment; + } + + /** + * Retrieves a scheme object corresponding to the URI's scheme/default + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI + */ + public function getSchemeObj($config, $context) + { + $registry = HTMLPurifier_URISchemeRegistry::instance(); + if ($this->scheme !== null) { + $scheme_obj = $registry->getScheme($this->scheme, $config, $context); + if (!$scheme_obj) { + return false; + } // invalid scheme, clean it out + } else { + // no scheme: retrieve the default one + $def = $config->getDefinition('URI'); + $scheme_obj = $def->getDefaultScheme($config, $context); + if (!$scheme_obj) { + // something funky happened to the default scheme object + trigger_error( + 'Default scheme object "' . $def->defaultScheme . '" was not readable', + E_USER_WARNING + ); + return false; + } + } + return $scheme_obj; + } + + /** + * Generic validation method applicable for all schemes. May modify + * this URI in order to get it into a compliant form. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool True if validation/filtering succeeds, false if failure + */ + public function validate($config, $context) + { + // ABNF definitions from RFC 3986 + $chars_sub_delims = '!$&\'()*+,;='; + $chars_gen_delims = ':/?#[]@'; + $chars_pchar = $chars_sub_delims . ':@'; + + // validate host + if (!is_null($this->host)) { + $host_def = new HTMLPurifier_AttrDef_URI_Host(); + $this->host = $host_def->validate($this->host, $config, $context); + if ($this->host === false) { + $this->host = null; + } + } + + // validate scheme + // NOTE: It's not appropriate to check whether or not this + // scheme is in our registry, since a URIFilter may convert a + // URI that we don't allow into one we do. So instead, we just + // check if the scheme can be dropped because there is no host + // and it is our default scheme. + if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') { + // support for relative paths is pretty abysmal when the + // scheme is present, so axe it when possible + $def = $config->getDefinition('URI'); + if ($def->defaultScheme === $this->scheme) { + $this->scheme = null; + } + } + + // validate username + if (!is_null($this->userinfo)) { + $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':'); + $this->userinfo = $encoder->encode($this->userinfo); + } + + // validate port + if (!is_null($this->port)) { + if ($this->port < 1 || $this->port > 65535) { + $this->port = null; + } + } + + // validate path + $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/'); + if (!is_null($this->host)) { // this catches $this->host === '' + // path-abempty (hier and relative) + // http://www.example.com/my/path + // //www.example.com/my/path (looks odd, but works, and + // recognized by most browsers) + // (this set is valid or invalid on a scheme by scheme + // basis, so we'll deal with it later) + // file:///my/path + // ///my/path + $this->path = $segments_encoder->encode($this->path); + } elseif ($this->path !== '') { + if ($this->path[0] === '/') { + // path-absolute (hier and relative) + // http:/my/path + // /my/path + if (strlen($this->path) >= 2 && $this->path[1] === '/') { + // This could happen if both the host gets stripped + // out + // http://my/path + // //my/path + $this->path = ''; + } else { + $this->path = $segments_encoder->encode($this->path); + } + } elseif (!is_null($this->scheme)) { + // path-rootless (hier) + // http:my/path + // Short circuit evaluation means we don't need to check nz + $this->path = $segments_encoder->encode($this->path); + } else { + // path-noscheme (relative) + // my/path + // (once again, not checking nz) + $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@'); + $c = strpos($this->path, '/'); + if ($c !== false) { + $this->path = + $segment_nc_encoder->encode(substr($this->path, 0, $c)) . + $segments_encoder->encode(substr($this->path, $c)); + } else { + $this->path = $segment_nc_encoder->encode($this->path); + } + } + } else { + // path-empty (hier and relative) + $this->path = ''; // just to be safe + } + + // qf = query and fragment + $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?'); + + if (!is_null($this->query)) { + $this->query = $qf_encoder->encode($this->query); + } + + if (!is_null($this->fragment)) { + $this->fragment = $qf_encoder->encode($this->fragment); + } + return true; + } + + /** + * Convert URI back to string + * @return string URI appropriate for output + */ + public function toString() + { + // reconstruct authority + $authority = null; + // there is a rendering difference between a null authority + // (http:foo-bar) and an empty string authority + // (http:///foo-bar). + if (!is_null($this->host)) { + $authority = ''; + if (!is_null($this->userinfo)) { + $authority .= $this->userinfo . '@'; + } + $authority .= $this->host; + if (!is_null($this->port)) { + $authority .= ':' . $this->port; + } + } + + // Reconstruct the result + // One might wonder about parsing quirks from browsers after + // this reconstruction. Unfortunately, parsing behavior depends + // on what *scheme* was employed (file:///foo is handled *very* + // differently than http:///foo), so unfortunately we have to + // defer to the schemes to do the right thing. + $result = ''; + if (!is_null($this->scheme)) { + $result .= $this->scheme . ':'; + } + if (!is_null($authority)) { + $result .= '//' . $authority; + } + $result .= $this->path; + if (!is_null($this->query)) { + $result .= '?' . $this->query; + } + if (!is_null($this->fragment)) { + $result .= '#' . $this->fragment; + } + + return $result; + } + + /** + * Returns true if this URL might be considered a 'local' URL given + * the current context. This is true when the host is null, or + * when it matches the host supplied to the configuration. + * + * Note that this does not do any scheme checking, so it is mostly + * only appropriate for metadata that doesn't care about protocol + * security. isBenign is probably what you actually want. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isLocal($config, $context) + { + if ($this->host === null) { + return true; + } + $uri_def = $config->getDefinition('URI'); + if ($uri_def->host === $this->host) { + return true; + } + return false; + } + + /** + * Returns true if this URL should be considered a 'benign' URL, + * that is: + * + * - It is a local URL (isLocal), and + * - It has a equal or better level of security + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isBenign($config, $context) + { + if (!$this->isLocal($config, $context)) { + return false; + } + + $scheme_obj = $this->getSchemeObj($config, $context); + if (!$scheme_obj) { + return false; + } // conservative approach + + $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context); + if ($current_scheme_obj->secure) { + if (!$scheme_obj->secure) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIDefinition.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php similarity index 70% rename from library/HTMLPurifier/URIDefinition.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php index ea2b8fe245..e0bd8bcca8 100644 --- a/library/HTMLPurifier/URIDefinition.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php @@ -23,19 +23,24 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition */ public $defaultScheme; - public function __construct() { + public function __construct() + { $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal()); $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources()); $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist()); + $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe()); $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute()); $this->registerFilter(new HTMLPurifier_URIFilter_Munge()); } - public function registerFilter($filter) { + public function registerFilter($filter) + { $this->registeredFilters[$filter->name] = $filter; } - public function addFilter($filter, $config) { + public function addFilter($filter, $config) + { $r = $filter->prepare($config); if ($r === false) return; // null is ok, for backwards compat if ($filter->post) { @@ -45,22 +50,29 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition } } - protected function doSetup($config) { + protected function doSetup($config) + { $this->setupMemberVariables($config); $this->setupFilters($config); } - protected function setupFilters($config) { + protected function setupFilters($config) + { foreach ($this->registeredFilters as $name => $filter) { - $conf = $config->get('URI.' . $name); - if ($conf !== false && $conf !== null) { + if ($filter->always_load) { $this->addFilter($filter, $config); + } else { + $conf = $config->get('URI.' . $name); + if ($conf !== false && $conf !== null) { + $this->addFilter($filter, $config); + } } } unset($this->registeredFilters); } - protected function setupMemberVariables($config) { + protected function setupMemberVariables($config) + { $this->host = $config->get('URI.Host'); $base_uri = $config->get('URI.Base'); if (!is_null($base_uri)) { @@ -72,7 +84,13 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme'); } - public function filter(&$uri, $config, $context) { + public function getDefaultScheme($config, $context) + { + return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context); + } + + public function filter(&$uri, $config, $context) + { foreach ($this->filters as $name => $f) { $result = $f->filter($uri, $config, $context); if (!$result) return false; @@ -80,7 +98,8 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition return true; } - public function postFilter(&$uri, $config, $context) { + public function postFilter(&$uri, $config, $context) + { foreach ($this->postFilters as $name => $f) { $result = $f->filter($uri, $config, $context); if (!$result) return false; diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php new file mode 100644 index 0000000000..09724e9f41 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php @@ -0,0 +1,74 @@ +<?php + +/** + * Chainable filters for custom URI processing. + * + * These filters can perform custom actions on a URI filter object, + * including transformation or blacklisting. A filter named Foo + * must have a corresponding configuration directive %URI.Foo, + * unless always_load is specified to be true. + * + * The following contexts may be available while URIFilters are being + * processed: + * + * - EmbeddedURI: true if URI is an embedded resource that will + * be loaded automatically on page load + * - CurrentToken: a reference to the token that is currently + * being processed + * - CurrentAttr: the name of the attribute that is currently being + * processed + * - CurrentCSSProperty: the name of the CSS property that is + * currently being processed (if applicable) + * + * @warning This filter is called before scheme object validation occurs. + * Make sure, if you require a specific scheme object, you + * you check that it exists. This allows filters to convert + * proprietary URI schemes into regular ones. + */ +abstract class HTMLPurifier_URIFilter +{ + + /** + * Unique identifier of filter. + * @type string + */ + public $name; + + /** + * True if this filter should be run after scheme validation. + * @type bool + */ + public $post = false; + + /** + * True if this filter should always be loaded. + * This permits a filter to be named Foo without the corresponding + * %URI.Foo directive existing. + * @type bool + */ + public $always_load = false; + + /** + * Performs initialization for the filter. If the filter returns + * false, this means that it shouldn't be considered active. + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + return true; + } + + /** + * Filter a URI object + * @param HTMLPurifier_URI $uri Reference to URI object variable + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool Whether or not to continue processing: false indicates + * URL is no good, true indicates continue processing. Note that + * all changes are committed directly on the URI object + */ + abstract public function filter(&$uri, $config, $context); +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php new file mode 100644 index 0000000000..ced1b13763 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternal.php @@ -0,0 +1,54 @@ +<?php + +class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'DisableExternal'; + + /** + * @type array + */ + protected $ourHostParts = false; + + /** + * @param HTMLPurifier_Config $config + * @return void + */ + public function prepare($config) + { + $our_host = $config->getDefinition('URI')->host; + if ($our_host !== null) { + $this->ourHostParts = array_reverse(explode('.', $our_host)); + } + } + + /** + * @param HTMLPurifier_URI $uri Reference + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($uri->host)) { + return true; + } + if ($this->ourHostParts === false) { + return false; + } + $host_parts = array_reverse(explode('.', $uri->host)); + foreach ($this->ourHostParts as $i => $x) { + if (!isset($host_parts[$i])) { + return false; + } + if ($host_parts[$i] != $this->ourHostParts[$i]) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php new file mode 100644 index 0000000000..c6562169e0 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php @@ -0,0 +1,25 @@ +<?php + +class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal +{ + /** + * @type string + */ + public $name = 'DisableExternalResources'; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (!$context->get('EmbeddedURI', true)) { + return true; + } + return parent::filter($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php new file mode 100644 index 0000000000..d5c412c444 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php @@ -0,0 +1,22 @@ +<?php + +class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'DisableResources'; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + return !$context->get('EmbeddedURI', true); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php new file mode 100644 index 0000000000..a6645c17ee --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php @@ -0,0 +1,46 @@ +<?php + +// It's not clear to me whether or not Punycode means that hostnames +// do not have canonical forms anymore. As far as I can tell, it's +// not a problem (punycoding should be identity when no Unicode +// points are involved), but I'm not 100% sure +class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'HostBlacklist'; + + /** + * @type array + */ + protected $blacklist = array(); + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->blacklist = $config->get('URI.HostBlacklist'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + foreach ($this->blacklist as $blacklisted_host_fragment) { + if (strpos($uri->host, $blacklisted_host_fragment) !== false) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php similarity index 71% rename from library/HTMLPurifier/URIFilter/MakeAbsolute.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php index f46ab2630d..c507bbff8e 100644 --- a/library/HTMLPurifier/URIFilter/MakeAbsolute.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php @@ -4,14 +4,35 @@ class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter { + /** + * @type string + */ public $name = 'MakeAbsolute'; + + /** + * @type + */ protected $base; + + /** + * @type array + */ protected $basePathStack = array(); - public function prepare($config) { + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { $def = $config->getDefinition('URI'); $this->base = $def->base; if (is_null($this->base)) { - trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_WARNING); + trigger_error( + 'URI.MakeAbsolute is being ignored due to lack of ' . + 'value for URI.Base configuration', + E_USER_WARNING + ); return false; } $this->base->fragment = null; // fragment is invalid for base URI @@ -21,19 +42,29 @@ class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter $this->basePathStack = $stack; return true; } - public function filter(&$uri, $config, $context) { - if (is_null($this->base)) return true; // abort early - if ( - $uri->path === '' && is_null($uri->scheme) && - is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment) - ) { + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($this->base)) { + return true; + } // abort early + if ($uri->path === '' && is_null($uri->scheme) && + is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) { // reference to current document $uri = clone $this->base; return true; } if (!is_null($uri->scheme)) { // absolute URI already: don't change - if (!is_null($uri->host)) return true; + if (!is_null($uri->host)) { + return true; + } $scheme_obj = $uri->getSchemeObj($config, $context); if (!$scheme_obj) { // scheme not recognized @@ -66,22 +97,33 @@ class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter } // re-combine $uri->scheme = $this->base->scheme; - if (is_null($uri->userinfo)) $uri->userinfo = $this->base->userinfo; - if (is_null($uri->host)) $uri->host = $this->base->host; - if (is_null($uri->port)) $uri->port = $this->base->port; + if (is_null($uri->userinfo)) { + $uri->userinfo = $this->base->userinfo; + } + if (is_null($uri->host)) { + $uri->host = $this->base->host; + } + if (is_null($uri->port)) { + $uri->port = $this->base->port; + } return true; } /** * Resolve dots and double-dots in a path stack + * @param array $stack + * @return array */ - private function _collapseStack($stack) { + private function _collapseStack($stack) + { $result = array(); $is_folder = false; for ($i = 0; isset($stack[$i]); $i++) { $is_folder = false; // absorb an internally duplicated slash - if ($stack[$i] == '' && $i && isset($stack[$i+1])) continue; + if ($stack[$i] == '' && $i && isset($stack[$i + 1])) { + continue; + } if ($stack[$i] == '..') { if (!empty($result)) { $segment = array_pop($result); @@ -106,7 +148,9 @@ class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter } $result[] = $stack[$i]; } - if ($is_folder) $result[] = ''; + if ($is_folder) { + $result[] = ''; + } return $result; } } diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php new file mode 100644 index 0000000000..6e03315a17 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php @@ -0,0 +1,115 @@ +<?php + +class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'Munge'; + + /** + * @type bool + */ + public $post = true; + + /** + * @type string + */ + private $target; + + /** + * @type HTMLPurifier_URIParser + */ + private $parser; + + /** + * @type bool + */ + private $doEmbed; + + /** + * @type string + */ + private $secretKey; + + /** + * @type array + */ + protected $replace = array(); + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->target = $config->get('URI.' . $this->name); + $this->parser = new HTMLPurifier_URIParser(); + $this->doEmbed = $config->get('URI.MungeResources'); + $this->secretKey = $config->get('URI.MungeSecretKey'); + if ($this->secretKey && !function_exists('hash_hmac')) { + throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support."); + } + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if ($context->get('EmbeddedURI', true) && !$this->doEmbed) { + return true; + } + + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + return true; + } // ignore unknown schemes, maybe another postfilter did it + if (!$scheme_obj->browsable) { + return true; + } // ignore non-browseable schemes, since we can't munge those in a reasonable way + if ($uri->isBenign($config, $context)) { + return true; + } // don't redirect if a benign URL + + $this->makeReplace($uri, $config, $context); + $this->replace = array_map('rawurlencode', $this->replace); + + $new_uri = strtr($this->target, $this->replace); + $new_uri = $this->parser->parse($new_uri); + // don't redirect if the target host is the same as the + // starting host + if ($uri->host === $new_uri->host) { + return true; + } + $uri = $new_uri; // overwrite + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + protected function makeReplace($uri, $config, $context) + { + $string = $uri->toString(); + // always available + $this->replace['%s'] = $string; + $this->replace['%r'] = $context->get('EmbeddedURI', true); + $token = $context->get('CurrentToken', true); + $this->replace['%n'] = $token ? $token->name : null; + $this->replace['%m'] = $context->get('CurrentAttr', true); + $this->replace['%p'] = $context->get('CurrentCSSProperty', true); + // not always available + if ($this->secretKey) { + $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php new file mode 100644 index 0000000000..f609c47a34 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php @@ -0,0 +1,68 @@ +<?php + +/** + * Implements safety checks for safe iframes. + * + * @warning This filter is *critical* for ensuring that %HTML.SafeIframe + * works safely. + */ +class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter +{ + /** + * @type string + */ + public $name = 'SafeIframe'; + + /** + * @type bool + */ + public $always_load = true; + + /** + * @type string + */ + protected $regexp = null; + + // XXX: The not so good bit about how this is all set up now is we + // can't check HTML.SafeIframe in the 'prepare' step: we have to + // defer till the actual filtering. + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function prepare($config) + { + $this->regexp = $config->get('URI.SafeIframeRegexp'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + // check if filter not applicable + if (!$config->get('HTML.SafeIframe')) { + return true; + } + // check if the filter should actually trigger + if (!$context->get('EmbeddedURI', true)) { + return true; + } + $token = $context->get('CurrentToken', true); + if (!($token && $token->name == 'iframe')) { + return true; + } + // check if we actually have some whitelists enabled + if ($this->regexp === null) { + return false; + } + // actually check the whitelists + return preg_match($this->regexp, $uri->toString()); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php similarity index 94% rename from library/HTMLPurifier/URIParser.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php index 7179e4ab89..0e7381a07b 100644 --- a/library/HTMLPurifier/URIParser.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php @@ -12,7 +12,8 @@ class HTMLPurifier_URIParser */ protected $percentEncoder; - public function __construct() { + public function __construct() + { $this->percentEncoder = new HTMLPurifier_PercentEncoder(); } @@ -22,15 +23,15 @@ class HTMLPurifier_URIParser * @return HTMLPurifier_URI representation of URI. This representation has * not been validated yet and may not conform to RFC. */ - public function parse($uri) { - + public function parse($uri) + { $uri = $this->percentEncoder->normalize($uri); // Regexp is as per Appendix B. // Note that ["<>] are an addition to the RFC's recommended // characters, because they represent external delimeters. $r_URI = '!'. - '(([^:/?#"<>]+):)?'. // 2. Scheme + '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme '(//([^/?#"<>]*))?'. // 4. Authority '([^?#"<>]*)'. // 5. Path '(\?([^#"<>]*))?'. // 7. Query diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php new file mode 100644 index 0000000000..fe9e82cf26 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php @@ -0,0 +1,102 @@ +<?php + +/** + * Validator for the components of a URI for a specific scheme + */ +abstract class HTMLPurifier_URIScheme +{ + + /** + * Scheme's default port (integer). If an explicit port number is + * specified that coincides with the default port, it will be + * elided. + * @type int + */ + public $default_port = null; + + /** + * Whether or not URIs of this scheme are locatable by a browser + * http and ftp are accessible, while mailto and news are not. + * @type bool + */ + public $browsable = false; + + /** + * Whether or not data transmitted over this scheme is encrypted. + * https is secure, http is not. + * @type bool + */ + public $secure = false; + + /** + * Whether or not the URI always uses <hier_part>, resolves edge cases + * with making relative URIs absolute + * @type bool + */ + public $hierarchical = false; + + /** + * Whether or not the URI may omit a hostname when the scheme is + * explicitly specified, ala file:///path/to/file. As of writing, + * 'file' is the only scheme that browsers support his properly. + * @type bool + */ + public $may_omit_host = false; + + /** + * Validates the components of a URI for a specific scheme. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + abstract public function doValidate(&$uri, $config, $context); + + /** + * Public interface for validating components of a URI. Performs a + * bunch of default actions. Don't overload this method. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + public function validate(&$uri, $config, $context) + { + if ($this->default_port == $uri->port) { + $uri->port = null; + } + // kludge: browsers do funny things when the scheme but not the + // authority is set + if (!$this->may_omit_host && + // if the scheme is present, a missing host is always in error + (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) || + // if the scheme is not present, a *blank* host is in error, + // since this translates into '///path' which most browsers + // interpret as being 'http://path'. + (is_null($uri->scheme) && $uri->host === '') + ) { + do { + if (is_null($uri->scheme)) { + if (substr($uri->path, 0, 2) != '//') { + $uri->host = null; + break; + } + // URI is '////path', so we cannot nullify the + // host to preserve semantics. Try expanding the + // hostname instead (fall through) + } + // first see if we can manually insert a hostname + $host = $config->get('URI.Host'); + if (!is_null($host)) { + $uri->host = $host; + } else { + // we can't do anything sensible, reject the URL. + return false; + } + } while (false); + } + return $this->doValidate($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme/data.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php similarity index 74% rename from library/HTMLPurifier/URIScheme/data.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php index b7f1989cbf..6ebca49848 100644 --- a/library/HTMLPurifier/URIScheme/data.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php @@ -3,18 +3,38 @@ /** * Implements data: URI for base64 encoded images supported by GD. */ -class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ public $browsable = true; + + /** + * @type array + */ public $allowed_types = array( // you better write validation code for other types if you // decide to allow them 'image/jpeg' => true, 'image/gif' => true, 'image/png' => true, - ); + ); + // this is actually irrelevant since we only write out the path + // component + /** + * @type bool + */ + public $may_omit_host = true; - public function validate(&$uri, $config, $context) { + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $result = explode(',', $uri->path, 2); $is_base64 = false; $charset = null; @@ -23,7 +43,7 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { list($metadata, $data) = $result; // do some legwork on the metadata $metas = explode(';', $metadata); - while(!empty($metas)) { + while (!empty($metas)) { $cur = array_shift($metas); if ($cur == 'base64') { $is_base64 = true; @@ -32,10 +52,14 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { if (substr($cur, 0, 8) == 'charset=') { // doesn't match if there are arbitrary spaces, but // whatever dude - if ($charset !== null) continue; // garbage + if ($charset !== null) { + continue; + } // garbage $charset = substr($cur, 8); // not used } else { - if ($content_type !== null) continue; // garbage + if ($content_type !== null) { + continue; + } // garbage $content_type = $cur; } } @@ -61,11 +85,15 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { file_put_contents($file, $raw_data); if (function_exists('exif_imagetype')) { $image_code = exif_imagetype($file); + unlink($file); } elseif (function_exists('getimagesize')) { set_error_handler(array($this, 'muteErrorHandler')); $info = getimagesize($file); restore_error_handler(); - if ($info == false) return false; + unlink($file); + if ($info == false) { + return false; + } $image_code = $info[2]; } else { trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); @@ -74,7 +102,9 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { if ($real_content_type != $content_type) { // we're nice guys; if the content type is something else we // support, change it over - if (empty($this->allowed_types[$real_content_type])) return false; + if (empty($this->allowed_types[$real_content_type])) { + return false; + } $content_type = $real_content_type; } // ok, it's kosher, rewrite what we need @@ -87,7 +117,11 @@ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { return true; } - public function muteErrorHandler($errno, $errstr) {} - + /** + * @param int $errno + * @param string $errstr + */ + public function muteErrorHandler($errno, $errstr) + { + } } - diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php new file mode 100644 index 0000000000..215be4ba80 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php @@ -0,0 +1,44 @@ +<?php + +/** + * Validates file as defined by RFC 1630 and RFC 1738. + */ +class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme +{ + /** + * Generally file:// URLs are not accessible from most + * machines, so placing them as an img src is incorrect. + * @type bool + */ + public $browsable = false; + + /** + * Basically the *only* URI scheme for which this is true, since + * accessing files on the local machine is very common. In fact, + * browsers on some operating systems don't understand the + * authority, though I hear it is used on Windows to refer to + * network shares. + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + // Authentication method is not supported + $uri->userinfo = null; + // file:// makes no provisions for accessing the resource + $uri->port = null; + // While it seems to work on Firefox, the querystring has + // no possible effect and is thus stripped. + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme/ftp.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php similarity index 74% rename from library/HTMLPurifier/URIScheme/ftp.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php index 5849bf7ff0..1eb43ee5c4 100644 --- a/library/HTMLPurifier/URIScheme/ftp.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php @@ -3,15 +3,32 @@ /** * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738. */ -class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ public $default_port = 21; + + /** + * @type bool + */ public $browsable = true; // usually + + /** + * @type bool + */ public $hierarchical = true; - public function validate(&$uri, $config, $context) { - parent::validate($uri, $config, $context); - $uri->query = null; + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->query = null; // typecode check $semicolon_pos = strrpos($uri->path, ';'); // reverse @@ -34,10 +51,8 @@ class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme { $uri->path = str_replace(';', '%3B', $uri->path); $uri->path .= $type_ret; } - return true; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme/http.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php similarity index 50% rename from library/HTMLPurifier/URIScheme/http.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php index b097a31d6a..ce69ec438a 100644 --- a/library/HTMLPurifier/URIScheme/http.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php @@ -3,18 +3,34 @@ /** * Validates http (HyperText Transfer Protocol) as defined by RFC 2616 */ -class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ public $default_port = 80; + + /** + * @type bool + */ public $browsable = true; + + /** + * @type bool + */ public $hierarchical = true; - public function validate(&$uri, $config, $context) { - parent::validate($uri, $config, $context); + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $uri->userinfo = null; return true; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme/https.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php similarity index 65% rename from library/HTMLPurifier/URIScheme/https.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php index 29e380919f..0e96882db9 100644 --- a/library/HTMLPurifier/URIScheme/https.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php @@ -3,10 +3,16 @@ /** * Validates https (Secure HTTP) according to http scheme. */ -class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http { - +class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http +{ + /** + * @type int + */ public $default_port = 443; - + /** + * @type bool + */ + public $secure = true; } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URIScheme/mailto.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php similarity index 63% rename from library/HTMLPurifier/URIScheme/mailto.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php index c1e2cd5aae..c3a6b602a7 100644 --- a/library/HTMLPurifier/URIScheme/mailto.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/mailto.php @@ -9,19 +9,32 @@ * @todo Filter allowed query parameters */ -class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme { - +class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ public $browsable = false; - public function validate(&$uri, $config, $context) { - parent::validate($uri, $config, $context); + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { $uri->userinfo = null; $uri->host = null; $uri->port = null; // we need to validate path against RFC 2368's addr-spec return true; } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php new file mode 100644 index 0000000000..7490927d63 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php @@ -0,0 +1,35 @@ +<?php + +/** + * Validates news (Usenet) as defined by generic RFC 1738 + */ +class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme +{ + /** + * @type bool + */ + public $browsable = false; + + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->query = null; + // typecode check needed on path + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php new file mode 100644 index 0000000000..f211d715e9 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php @@ -0,0 +1,32 @@ +<?php + +/** + * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738 + */ +class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme +{ + /** + * @type int + */ + public $default_port = 119; + + /** + * @type bool + */ + public $browsable = false; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $uri->userinfo = null; + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/URISchemeRegistry.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php similarity index 58% rename from library/HTMLPurifier/URISchemeRegistry.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php index 576bf7b6d1..4ac8a0b76e 100644 --- a/library/HTMLPurifier/URISchemeRegistry.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php @@ -8,12 +8,14 @@ class HTMLPurifier_URISchemeRegistry /** * Retrieve sole instance of the registry. - * @param $prototype Optional prototype to overload sole instance with, + * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with, * or bool true to reset to default registry. + * @return HTMLPurifier_URISchemeRegistry * @note Pass a registry object $prototype with a compatible interface and * the function will copy it and return it all further times. */ - public static function instance($prototype = null) { + public static function instance($prototype = null) + { static $instance = null; if ($prototype !== null) { $instance = $prototype; @@ -25,17 +27,22 @@ class HTMLPurifier_URISchemeRegistry /** * Cache of retrieved schemes. + * @type HTMLPurifier_URIScheme[] */ protected $schemes = array(); /** * Retrieves a scheme validator object - * @param $scheme String scheme name like http or mailto - * @param $config HTMLPurifier_Config object - * @param $config HTMLPurifier_Context object + * @param string $scheme String scheme name like http or mailto + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_URIScheme */ - public function getScheme($scheme, $config, $context) { - if (!$config) $config = HTMLPurifier_Config::createDefault(); + public function getScheme($scheme, $config, $context) + { + if (!$config) { + $config = HTMLPurifier_Config::createDefault(); + } // important, otherwise attacker could include arbitrary file $allowed_schemes = $config->get('URI.AllowedSchemes'); @@ -45,24 +52,30 @@ class HTMLPurifier_URISchemeRegistry return; } - if (isset($this->schemes[$scheme])) return $this->schemes[$scheme]; - if (!isset($allowed_schemes[$scheme])) return; + if (isset($this->schemes[$scheme])) { + return $this->schemes[$scheme]; + } + if (!isset($allowed_schemes[$scheme])) { + return; + } $class = 'HTMLPurifier_URIScheme_' . $scheme; - if (!class_exists($class)) return; + if (!class_exists($class)) { + return; + } $this->schemes[$scheme] = new $class(); return $this->schemes[$scheme]; } /** * Registers a custom scheme to the cache, bypassing reflection. - * @param $scheme Scheme name - * @param $scheme_obj HTMLPurifier_URIScheme object + * @param string $scheme Scheme name + * @param HTMLPurifier_URIScheme $scheme_obj */ - public function register($scheme, $scheme_obj) { + public function register($scheme, $scheme_obj) + { $this->schemes[$scheme] = $scheme_obj; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/UnitConverter.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php similarity index 75% rename from library/HTMLPurifier/UnitConverter.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php index 545d426220..166f3bf306 100644 --- a/library/HTMLPurifier/UnitConverter.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php @@ -37,20 +37,24 @@ class HTMLPurifier_UnitConverter /** * Minimum bcmath precision for output. + * @type int */ protected $outputPrecision; /** * Bcmath precision for internal calculations. + * @type int */ protected $internalPrecision; /** - * Whether or not BCMath is available + * Whether or not BCMath is available. + * @type bool */ private $bcmath; - public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) { + public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) + { $this->outputPrecision = $output_precision; $this->internalPrecision = $internal_precision; $this->bcmath = !$force_no_bcmath && function_exists('bcmul'); @@ -63,6 +67,7 @@ class HTMLPurifier_UnitConverter * it before passing it here! * @param string $to_unit * Unit to convert to. + * @return HTMLPurifier_Length|bool * @note * About precision: This conversion function pays very special * attention to the incoming precision of values and attempts @@ -74,11 +79,13 @@ class HTMLPurifier_UnitConverter * and this causes some decimals to be excluded, those * decimals will be added on. */ - public function convert($length, $to_unit) { + public function convert($length, $to_unit) + { + if (!$length->isValid()) { + return false; + } - if (!$length->isValid()) return false; - - $n = $length->getN(); + $n = $length->getN(); $unit = $length->getUnit(); if ($n === '0' || $unit === false) { @@ -87,21 +94,29 @@ class HTMLPurifier_UnitConverter $state = $dest_state = false; foreach (self::$units as $k => $x) { - if (isset($x[$unit])) $state = $k; - if (isset($x[$to_unit])) $dest_state = $k; + if (isset($x[$unit])) { + $state = $k; + } + if (isset($x[$to_unit])) { + $dest_state = $k; + } + } + if (!$state || !$dest_state) { + return false; } - if (!$state || !$dest_state) return false; // Some calculations about the initial precision of the number; // this will be useful when we need to do final rounding. $sigfigs = $this->getSigFigs($n); - if ($sigfigs < $this->outputPrecision) $sigfigs = $this->outputPrecision; + if ($sigfigs < $this->outputPrecision) { + $sigfigs = $this->outputPrecision; + } // BCMath's internal precision deals only with decimals. Use // our default if the initial number has no decimals, or increase // it by how ever many decimals, thus, the number of guard digits // will always be greater than or equal to internalPrecision. - $log = (int) floor(log(abs($n), 10)); + $log = (int)floor(log(abs($n), 10)); $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision for ($i = 0; $i < 2; $i++) { @@ -152,14 +167,18 @@ class HTMLPurifier_UnitConverter } // Post-condition: $unit == $to_unit - if ($unit !== $to_unit) return false; + if ($unit !== $to_unit) { + return false; + } // Useful for debugging: //echo "<pre>n"; //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n"; $n = $this->round($n, $sigfigs); - if (strpos($n, '.') !== false) $n = rtrim($n, '0'); + if (strpos($n, '.') !== false) { + $n = rtrim($n, '0'); + } $n = rtrim($n, '.'); return new HTMLPurifier_Length($n, $unit); @@ -170,53 +189,84 @@ class HTMLPurifier_UnitConverter * @param string $n Decimal number * @return int number of sigfigs */ - public function getSigFigs($n) { + public function getSigFigs($n) + { $n = ltrim($n, '0+-'); $dp = strpos($n, '.'); // decimal position if ($dp === false) { $sigfigs = strlen(rtrim($n, '0')); } else { $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character - if ($dp !== 0) $sigfigs--; + if ($dp !== 0) { + $sigfigs--; + } } return $sigfigs; } /** * Adds two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string */ - private function add($s1, $s2, $scale) { - if ($this->bcmath) return bcadd($s1, $s2, $scale); - else return $this->scale($s1 + $s2, $scale); + private function add($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcadd($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 + (float)$s2, $scale); + } } /** * Multiples two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string */ - private function mul($s1, $s2, $scale) { - if ($this->bcmath) return bcmul($s1, $s2, $scale); - else return $this->scale($s1 * $s2, $scale); + private function mul($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcmul($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 * (float)$s2, $scale); + } } /** * Divides two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string */ - private function div($s1, $s2, $scale) { - if ($this->bcmath) return bcdiv($s1, $s2, $scale); - else return $this->scale($s1 / $s2, $scale); + private function div($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcdiv($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 / (float)$s2, $scale); + } } /** * Rounds a number according to the number of sigfigs it should have, * using arbitrary precision when available. + * @param float $n + * @param int $sigfigs + * @return string */ - private function round($n, $sigfigs) { - $new_log = (int) floor(log(abs($n), 10)); // Number of digits left of decimal - 1 + private function round($n, $sigfigs) + { + $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1 $rp = $sigfigs - $new_log - 1; // Number of decimal places needed $neg = $n < 0 ? '-' : ''; // Negative sign if ($this->bcmath) { if ($rp >= 0) { - $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1); + $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1); $n = bcdiv($n, '1', $rp); } else { // This algorithm partially depends on the standardized @@ -232,23 +282,26 @@ class HTMLPurifier_UnitConverter /** * Scales a float to $scale digits right of decimal point, like BCMath. + * @param float $r + * @param int $scale + * @return string */ - private function scale($r, $scale) { + private function scale($r, $scale) + { if ($scale < 0) { // The f sprintf type doesn't support negative numbers, so we // need to cludge things manually. First get the string. - $r = sprintf('%.0f', (float) $r); + $r = sprintf('%.0f', (float)$r); // Due to floating point precision loss, $r will more than likely // look something like 4652999999999.9234. We grab one more digit // than we need to precise from $r and then use that to round // appropriately. - $precise = (string) round(substr($r, 0, strlen($r) + $scale), -1); + $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1); // Now we return it, truncating the zero that was rounded off. return substr($precise, 0, -1) . str_repeat('0', -$scale + 1); } - return sprintf('%.' . $scale . 'f', (float) $r); + return sprintf('%.' . $scale . 'f', (float)$r); } - } // vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php new file mode 100644 index 0000000000..50cba6910d --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php @@ -0,0 +1,198 @@ +<?php + +/** + * Parses string representations into their corresponding native PHP + * variable type. The base implementation does a simple type-check. + */ +class HTMLPurifier_VarParser +{ + + const STRING = 1; + const ISTRING = 2; + const TEXT = 3; + const ITEXT = 4; + const INT = 5; + const FLOAT = 6; + const BOOL = 7; + const LOOKUP = 8; + const ALIST = 9; + const HASH = 10; + const MIXED = 11; + + /** + * Lookup table of allowed types. Mainly for backwards compatibility, but + * also convenient for transforming string type names to the integer constants. + */ + public static $types = array( + 'string' => self::STRING, + 'istring' => self::ISTRING, + 'text' => self::TEXT, + 'itext' => self::ITEXT, + 'int' => self::INT, + 'float' => self::FLOAT, + 'bool' => self::BOOL, + 'lookup' => self::LOOKUP, + 'list' => self::ALIST, + 'hash' => self::HASH, + 'mixed' => self::MIXED + ); + + /** + * Lookup table of types that are string, and can have aliases or + * allowed value lists. + */ + public static $stringTypes = array( + self::STRING => true, + self::ISTRING => true, + self::TEXT => true, + self::ITEXT => true, + ); + + /** + * Validate a variable according to type. + * It may return NULL as a valid type if $allow_null is true. + * + * @param mixed $var Variable to validate + * @param int $type Type of variable, see HTMLPurifier_VarParser->types + * @param bool $allow_null Whether or not to permit null as a value + * @return string Validated and type-coerced variable + * @throws HTMLPurifier_VarParserException + */ + final public function parse($var, $type, $allow_null = false) + { + if (is_string($type)) { + if (!isset(HTMLPurifier_VarParser::$types[$type])) { + throw new HTMLPurifier_VarParserException("Invalid type '$type'"); + } else { + $type = HTMLPurifier_VarParser::$types[$type]; + } + } + $var = $this->parseImplementation($var, $type, $allow_null); + if ($allow_null && $var === null) { + return null; + } + // These are basic checks, to make sure nothing horribly wrong + // happened in our implementations. + switch ($type) { + case (self::STRING): + case (self::ISTRING): + case (self::TEXT): + case (self::ITEXT): + if (!is_string($var)) { + break; + } + if ($type == self::ISTRING || $type == self::ITEXT) { + $var = strtolower($var); + } + return $var; + case (self::INT): + if (!is_int($var)) { + break; + } + return $var; + case (self::FLOAT): + if (!is_float($var)) { + break; + } + return $var; + case (self::BOOL): + if (!is_bool($var)) { + break; + } + return $var; + case (self::LOOKUP): + case (self::ALIST): + case (self::HASH): + if (!is_array($var)) { + break; + } + if ($type === self::LOOKUP) { + foreach ($var as $k) { + if ($k !== true) { + $this->error('Lookup table contains value other than true'); + } + } + } elseif ($type === self::ALIST) { + $keys = array_keys($var); + if (array_keys($keys) !== $keys) { + $this->error('Indices for list are not uniform'); + } + } + return $var; + case (self::MIXED): + return $var; + default: + $this->errorInconsistent(get_class($this), $type); + } + $this->errorGeneric($var, $type); + } + + /** + * Actually implements the parsing. Base implementation does not + * do anything to $var. Subclasses should overload this! + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return string + */ + protected function parseImplementation($var, $type, $allow_null) + { + return $var; + } + + /** + * Throws an exception. + * @throws HTMLPurifier_VarParserException + */ + protected function error($msg) + { + throw new HTMLPurifier_VarParserException($msg); + } + + /** + * Throws an inconsistency exception. + * @note This should not ever be called. It would be called if we + * extend the allowed values of HTMLPurifier_VarParser without + * updating subclasses. + * @param string $class + * @param int $type + * @throws HTMLPurifier_Exception + */ + protected function errorInconsistent($class, $type) + { + throw new HTMLPurifier_Exception( + "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) . + " not implemented" + ); + } + + /** + * Generic error for if a type didn't work. + * @param mixed $var + * @param int $type + */ + protected function errorGeneric($var, $type) + { + $vtype = gettype($var); + $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype"); + } + + /** + * @param int $type + * @return string + */ + public static function getTypeName($type) + { + static $lookup; + if (!$lookup) { + // Lazy load the alternative lookup table + $lookup = array_flip(HTMLPurifier_VarParser::$types); + } + if (!isset($lookup[$type])) { + return 'unknown'; + } + return $lookup[$type]; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/VarParser/Flexible.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php similarity index 56% rename from library/HTMLPurifier/VarParser/Flexible.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php index c954250e9f..b15016c5b2 100644 --- a/library/HTMLPurifier/VarParser/Flexible.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php @@ -7,28 +7,41 @@ */ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser { - - protected function parseImplementation($var, $type, $allow_null) { - if ($allow_null && $var === null) return null; + /** + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return array|bool|float|int|mixed|null|string + * @throws HTMLPurifier_VarParserException + */ + protected function parseImplementation($var, $type, $allow_null) + { + if ($allow_null && $var === null) { + return null; + } switch ($type) { // Note: if code "breaks" from the switch, it triggers a generic // exception to be thrown. Specific errors can be specifically // done here. - case self::MIXED : - case self::ISTRING : - case self::STRING : - case self::TEXT : - case self::ITEXT : + case self::MIXED: + case self::ISTRING: + case self::STRING: + case self::TEXT: + case self::ITEXT: return $var; - case self::INT : - if (is_string($var) && ctype_digit($var)) $var = (int) $var; + case self::INT: + if (is_string($var) && ctype_digit($var)) { + $var = (int)$var; + } return $var; - case self::FLOAT : - if ((is_string($var) && is_numeric($var)) || is_int($var)) $var = (float) $var; + case self::FLOAT: + if ((is_string($var) && is_numeric($var)) || is_int($var)) { + $var = (float)$var; + } return $var; - case self::BOOL : + case self::BOOL: if (is_int($var) && ($var === 0 || $var === 1)) { - $var = (bool) $var; + $var = (bool)$var; } elseif (is_string($var)) { if ($var == 'on' || $var == 'true' || $var == '1') { $var = true; @@ -39,48 +52,70 @@ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser } } return $var; - case self::ALIST : - case self::HASH : - case self::LOOKUP : + case self::ALIST: + case self::HASH: + case self::LOOKUP: if (is_string($var)) { // special case: technically, this is an array with // a single empty string item, but having an empty // array is more intuitive - if ($var == '') return array(); + if ($var == '') { + return array(); + } if (strpos($var, "\n") === false && strpos($var, "\r") === false) { // simplistic string to array method that only works // for simple lists of tag names or alphanumeric characters - $var = explode(',',$var); + $var = explode(',', $var); } else { $var = preg_split('/(,|[\n\r]+)/', $var); } // remove spaces - foreach ($var as $i => $j) $var[$i] = trim($j); + foreach ($var as $i => $j) { + $var[$i] = trim($j); + } if ($type === self::HASH) { // key:value,key2:value2 $nvar = array(); foreach ($var as $keypair) { $c = explode(':', $keypair, 2); - if (!isset($c[1])) continue; - $nvar[$c[0]] = $c[1]; + if (!isset($c[1])) { + continue; + } + $nvar[trim($c[0])] = trim($c[1]); } $var = $nvar; } } - if (!is_array($var)) break; + if (!is_array($var)) { + break; + } $keys = array_keys($var); if ($keys === array_keys($keys)) { - if ($type == self::ALIST) return $var; - elseif ($type == self::LOOKUP) { + if ($type == self::ALIST) { + return $var; + } elseif ($type == self::LOOKUP) { $new = array(); foreach ($var as $key) { $new[$key] = true; } return $new; - } else break; + } else { + break; + } + } + if ($type === self::ALIST) { + trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); + return array_values($var); } if ($type === self::LOOKUP) { foreach ($var as $key => $value) { + if ($value !== true) { + trigger_error( + "Lookup array has non-true value at key '$key'; " . + "maybe your input array was not indexed numerically", + E_USER_WARNING + ); + } $var[$key] = true; } } @@ -90,7 +125,6 @@ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser } $this->errorGeneric($var, $type); } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/VarParser/Native.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php similarity index 67% rename from library/HTMLPurifier/VarParser/Native.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php index b02a6de54c..f11c318efb 100644 --- a/library/HTMLPurifier/VarParser/Native.php +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php @@ -8,11 +8,24 @@ class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser { - protected function parseImplementation($var, $type, $allow_null) { + /** + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return null|string + */ + protected function parseImplementation($var, $type, $allow_null) + { return $this->evalExpression($var); } - protected function evalExpression($expr) { + /** + * @param string $expr + * @return mixed + * @throws HTMLPurifier_VarParserException + */ + protected function evalExpression($expr) + { $var = null; $result = eval("\$var = $expr;"); if ($result === false) { @@ -20,7 +33,6 @@ class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser } return $var; } - } // vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/VarParserException.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php similarity index 100% rename from library/HTMLPurifier/VarParserException.php rename to library/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php diff --git a/library/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php b/library/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php new file mode 100644 index 0000000000..6e21ea0703 --- /dev/null +++ b/library/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php @@ -0,0 +1,157 @@ +<?php + +/** + * A zipper is a purely-functional data structure which contains + * a focus that can be efficiently manipulated. It is known as + * a "one-hole context". This mutable variant implements a zipper + * for a list as a pair of two arrays, laid out as follows: + * + * Base list: 1 2 3 4 [ ] 6 7 8 9 + * Front list: 1 2 3 4 + * Back list: 9 8 7 6 + * + * User is expected to keep track of the "current element" and properly + * fill it back in as necessary. (ToDo: Maybe it's more user friendly + * to implicitly track the current element?) + * + * Nota bene: the current class gets confused if you try to store NULLs + * in the list. + */ + +class HTMLPurifier_Zipper +{ + public $front, $back; + + public function __construct($front, $back) { + $this->front = $front; + $this->back = $back; + } + + /** + * Creates a zipper from an array, with a hole in the + * 0-index position. + * @param Array to zipper-ify. + * @return Tuple of zipper and element of first position. + */ + static public function fromArray($array) { + $z = new self(array(), array_reverse($array)); + $t = $z->delete(); // delete the "dummy hole" + return array($z, $t); + } + + /** + * Convert zipper back into a normal array, optionally filling in + * the hole with a value. (Usually you should supply a $t, unless you + * are at the end of the array.) + */ + public function toArray($t = NULL) { + $a = $this->front; + if ($t !== NULL) $a[] = $t; + for ($i = count($this->back)-1; $i >= 0; $i--) { + $a[] = $this->back[$i]; + } + return $a; + } + + /** + * Move hole to the next element. + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function next($t) { + if ($t !== NULL) array_push($this->front, $t); + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Iterated hole advancement. + * @param $t Element to fill hole with + * @param $i How many forward to advance hole + * @return Original contents of new hole, i away + */ + public function advance($t, $n) { + for ($i = 0; $i < $n; $i++) { + $t = $this->next($t); + } + return $t; + } + + /** + * Move hole to the previous element + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function prev($t) { + if ($t !== NULL) array_push($this->back, $t); + return empty($this->front) ? NULL : array_pop($this->front); + } + + /** + * Delete contents of current hole, shifting hole to + * next element. + * @return Original contents of new hole. + */ + public function delete() { + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Returns true if we are at the end of the list. + * @return bool + */ + public function done() { + return empty($this->back); + } + + /** + * Insert element before hole. + * @param Element to insert + */ + public function insertBefore($t) { + if ($t !== NULL) array_push($this->front, $t); + } + + /** + * Insert element after hole. + * @param Element to insert + */ + public function insertAfter($t) { + if ($t !== NULL) array_push($this->back, $t); + } + + /** + * Splice in multiple elements at hole. Functional specification + * in terms of array_splice: + * + * $arr1 = $arr; + * $old1 = array_splice($arr1, $i, $delete, $replacement); + * + * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr); + * $t = $z->advance($t, $i); + * list($old2, $t) = $z->splice($t, $delete, $replacement); + * $arr2 = $z->toArray($t); + * + * assert($old1 === $old2); + * assert($arr1 === $arr2); + * + * NB: the absolute index location after this operation is + * *unchanged!* + * + * @param Current contents of hole. + */ + public function splice($t, $delete, $replacement) { + // delete + $old = array(); + $r = $t; + for ($i = $delete; $i > 0; $i--) { + $old[] = $r; + $r = $this->delete(); + } + // insert + for ($i = count($replacement)-1; $i >= 0; $i--) { + $this->insertAfter($r); + $r = $replacement[$i]; + } + return array($old, $r); + } +} diff --git a/library/ezyang/htmlpurifier/package.php b/library/ezyang/htmlpurifier/package.php new file mode 100644 index 0000000000..bfef93622d --- /dev/null +++ b/library/ezyang/htmlpurifier/package.php @@ -0,0 +1,61 @@ +<?php + +set_time_limit(0); + +require_once 'PEAR/PackageFileManager2.php'; +require_once 'PEAR/PackageFileManager/File.php'; +PEAR::setErrorHandling(PEAR_ERROR_PRINT); +$pkg = new PEAR_PackageFileManager2; + +$pkg->setOptions( + array( + 'baseinstalldir' => '/', + 'packagefile' => 'package.xml', + 'packagedirectory' => realpath(dirname(__FILE__) . '/library'), + 'filelistgenerator' => 'file', + 'include' => array('*'), + 'dir_roles' => array('/' => 'php'), // hack to put *.ser files in the right place + 'ignore' => array( + 'HTMLPurifier.standalone.php', + 'HTMLPurifier.path.php', + '*.tar.gz', + '*.tgz', + 'standalone/' + ), + ) +); + +$pkg->setPackage('HTMLPurifier'); +$pkg->setLicense('LGPL', 'http://www.gnu.org/licenses/lgpl.html'); +$pkg->setSummary('Standards-compliant HTML filter'); +$pkg->setDescription( + 'HTML Purifier is an HTML filter that will remove all malicious code + (better known as XSS) with a thoroughly audited, secure yet permissive + whitelist and will also make sure your documents are standards + compliant.' +); + +$pkg->addMaintainer('lead', 'ezyang', 'Edward Z. Yang', 'admin@htmlpurifier.org', 'yes'); + +$version = trim(file_get_contents('VERSION')); +$api_version = substr($version, 0, strrpos($version, '.')); + +$pkg->setChannel('htmlpurifier.org'); +$pkg->setAPIVersion($api_version); +$pkg->setAPIStability('stable'); +$pkg->setReleaseVersion($version); +$pkg->setReleaseStability('stable'); + +$pkg->addRelease(); + +$pkg->setNotes(file_get_contents('WHATSNEW')); +$pkg->setPackageType('php'); + +$pkg->setPhpDep('5.0.0'); +$pkg->setPearinstallerDep('1.4.3'); + +$pkg->generateContents(); + +$pkg->writePackageFile(); + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/phpdoc.ini b/library/ezyang/htmlpurifier/phpdoc.ini new file mode 100644 index 0000000000..c4c3723538 --- /dev/null +++ b/library/ezyang/htmlpurifier/phpdoc.ini @@ -0,0 +1,102 @@ +;; phpDocumentor parse configuration file +;; +;; This file is designed to cut down on repetitive typing on the command-line or web interface +;; You can copy this file to create a number of configuration files that can be used with the +;; command-line switch -c, as in phpdoc -c default.ini or phpdoc -c myini.ini. The web +;; interface will automatically generate a list of .ini files that can be used. +;; +;; default.ini is used to generate the online manual at http://www.phpdoc.org/docs +;; +;; ALL .ini files must be in the user subdirectory of phpDocumentor with an extension of .ini +;; +;; Copyright 2002, Greg Beaver <cellog@users.sourceforge.net> +;; +;; WARNING: do not change the name of any command-line parameters, phpDocumentor will ignore them + +[Parse Data] +;; title of all the documentation +;; legal values: any string +title = HTML Purifier API Documentation + +;; parse files that start with a . like .bash_profile +;; legal values: true, false +hidden = false + +;; show elements marked @access private in documentation by setting this to on +;; legal values: on, off +parseprivate = off + +;; parse with javadoc-like description (first sentence is always the short description) +;; legal values: on, off +javadocdesc = on + +;; add any custom @tags separated by commas here +;; legal values: any legal tagname separated by commas. +;customtags = mytag1,mytag2 + +;; This is only used by the XML:DocBook/peardoc2 converter +defaultcategoryname = Documentation + +;; what is the main package? +;; legal values: alphanumeric string plus - and _ +defaultpackagename = HTMLPurifier + +;; output any parsing information? set to on for cron jobs +;; legal values: on +;quiet = on + +;; parse a PEAR-style repository. Do not turn this on if your project does +;; not have a parent directory named "pear" +;; legal values: on/off +;pear = on + +;; where should the documentation be written? +;; legal values: a legal path +target = docs/phpdoc + +;; Which files should be parsed out as special documentation files, such as README, +;; INSTALL and CHANGELOG? This overrides the default files found in +;; phpDocumentor.ini (this file is not a user .ini file, but the global file) +readmeinstallchangelog = README, INSTALL, NEWS, WYSIWYG, SLOW, LICENSE, CREDITS + +;; limit output to the specified packages, even if others are parsed +;; legal values: package names separated by commas +;packageoutput = package1,package2 + +;; comma-separated list of files to parse +;; legal values: paths separated by commas +;filename = /path/to/file1,/path/to/file2,fileincurrentdirectory + +;; comma-separated list of directories to parse +;; legal values: directory paths separated by commas +;directory = /path1,/path2,.,..,subdirectory +;directory = /home/jeichorn/cvs/pear +directory = . + +;; template base directory (the equivalent directory of <installdir>/phpDocumentor) +;templatebase = /path/to/my/templates + +;; directory to find any example files in through @example and {@example} tags +;examplesdir = /path/to/my/templates + +;; comma-separated list of files, directories or wildcards ? and * (any wildcard) to ignore +;; legal values: any wildcard strings separated by commas +;ignore = /path/to/ignore*,*list.php,myfile.php,subdirectory/ +ignore = *tests*,*benchmarks*,*docs*,*test-settings.php,*configdoc*,*maintenance*,*smoketests*,*standalone*,*.svn*,*conf* + +sourcecode = on + +;; comma-separated list of Converters to use in outputformat:Convertername:templatedirectory format +;; legal values: HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib, +;; HTML:frames:earthli, +;; HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de, +;; HTML:frames:DOM/phphtmllib,HTML:frames:DOM/earthli +;; HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS +;; PDF:default:default,CHM:default:default,XML:DocBook/peardoc2:default +output=HTML:frames:default + +;; turn this option on if you want highlighted source code for every file +;; legal values: on/off +sourcecode = on + +; vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/modx.txt b/library/ezyang/htmlpurifier/plugins/modx.txt new file mode 100644 index 0000000000..0763821b50 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/modx.txt @@ -0,0 +1,112 @@ + +MODx Plugin + +MODx <http://www.modxcms.com/> is an open source PHP application framework. +I first came across them in my referrer logs when tillda asked if anyone +could implement an HTML Purifier plugin. This forum thread +<http://modxcms.com/forums/index.php/topic,6604.0.html> eventually resulted +in the fruition of this plugin that davidm says, "is on top of my favorite +list." HTML Purifier goes great with WYSIWYG editors! + + + +1. Credits + +PaulGregory wrote the overall structure of the code. I added the +slashes hack. + + + +2. Install + +First, you need to place HTML Purifier library somewhere. The code here +assumes that you've placed in MODx's assets/plugins/htmlpurifier (no version +number). + +Log into the manager, and navigate: + +Resources > Manage Resources > Plugins tab > New Plugin + +Type in a name (probably HTML Purifier), and copy paste this code into the +textarea: + +-------------------------------------------------------------------------------- +$e = &$modx->Event; +if ($e->name == 'OnBeforeDocFormSave') { + global $content; + + include_once '../assets/plugins/htmlpurifier/library/HTMLPurifier.auto.php'; + $purifier = new HTMLPurifier(); + + static $magic_quotes = null; + if ($magic_quotes === null) { + // this is an ugly hack because this hook hasn't + // had the backslashes removed yet when magic_quotes_gpc is on, + // but HTMLPurifier must not have the quotes slashed. + $magic_quotes = get_magic_quotes_gpc(); + } + + if ($magic_quotes) $content = stripslashes($content); + $content = $purifier->purify($content); + if ($magic_quotes) $content = addslashes($content); +} +-------------------------------------------------------------------------------- + +Then navigate to the System Events tab and check "OnBeforeDocFormSave". +Save the plugin. HTML Purifier now is integrated! + + + +3. Making sure it works + +You can test HTML Purifier by deliberately putting in crappy HTML and seeing +whether or not it gets fixed. A better way is to put in something like this: + +<p lang="fr">Il est bon</p> + +...and seeing whether or not the content comes out as: + +<p lang="fr" xml:lang="fr">Il est bon</p> + +(lang to xml:lang synchronization is one of the many features HTML Purifier +has). + + + +4. Caveat Emptor + +This code does not intercept save requests from the QuickEdit plugin, this may +be added in a later version. It also modifies things on save, so there's a +slight chance that HTML Purifier may make a boo-boo and accidently mess things +up (the original version is not saved). + +Finally, make sure that MODx is using UTF-8. If you are using, say, a French +localisation, you may be using Latin-1, if that's the case, configure +HTML Purifier properly like this: + +$config = HTMLPurifier_Config::createDefault(); +$config->set('Core', 'Encoding', 'ISO-8859-1'); // or whatever encoding +$purifier = new HTMLPurifier($config); + + + +5. Known Bugs + +'rn' characters sometimes mysteriously appear after purification. We are +currently investigating this issue. See: <http://htmlpurifier.org/phorum/read.php?3,1866> + + + +6. See Also + +A modified version of Jot 1.1.3 is available, which integrates with HTML +Purifier. You can check it out here: <http://modxcms.com/forums/index.php/topic,25621.msg161970.html> + + +X. Changelog + +2008-06-16 +- Updated code to work with 3.1.0 and later +- Add Known Bugs and See Also section + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/.gitignore b/library/ezyang/htmlpurifier/plugins/phorum/.gitignore new file mode 100644 index 0000000000..8325e09020 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/.gitignore @@ -0,0 +1,2 @@ +migrate.php +htmlpurifier/* diff --git a/library/ezyang/htmlpurifier/plugins/phorum/Changelog b/library/ezyang/htmlpurifier/plugins/phorum/Changelog new file mode 100644 index 0000000000..9f939e54ae --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/Changelog @@ -0,0 +1,27 @@ +Changelog HTMLPurifier : Phorum Mod +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + += KEY ==================== + # Breaks back-compat + ! Feature + - Bugfix + + Sub-comment + . Internal change +========================== + +Version 4.0.0 for Phorum 5.2, released July 9, 2009 +# Works only with HTML Purifier 4.0.0 +! Better installation documentation +- Fixed double encoded quotes +- Fixed fatal error when migrate.php is blank + +Version 3.0.0 for Phorum 5.2, released January 12, 2008 +# WYSIWYG and suppress_message options are now configurable via web + interface. +- Module now compatible with Phorum 5.2, primary bugs were in migration + code as well as signature and edit message handling. This module is NOT + compatible with Phorum 5.1. +- Buggy WYSIWYG mode refined +. AutoFormatParam added to list of default configuration namespaces + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/INSTALL b/library/ezyang/htmlpurifier/plugins/phorum/INSTALL new file mode 100644 index 0000000000..23c76fc5c6 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/INSTALL @@ -0,0 +1,84 @@ + +Install + How to install the Phorum HTML Purifier plugin + +0. PREREQUISITES +---------------- +This Phorum module only works on PHP5 and with HTML Purifier 4.0.0 +or later. + +1. UNZIP +-------- +Unzip phorum-htmlpurifier-x.y.z, producing an htmlpurifier folder. +You've already done this step if you're reading this! + +2. MOVE +------- +Move the htmlpurifier folder to the mods/ folder of your Phorum +installation, so the directory structure looks like: + +phorum/ + mods/ + htmlpurifier/ + INSTALL - this install file + info.txt, ... - the module files + htmlpurifier/ + +3. INSTALL HTML PURIFIER +------------------------ +Download and unzip HTML Purifier <htmlpurifier.org>. Place the contents of +the library/ folder in the htmlpurifier/htmlpurifier folder. Your directory +structure will look like: + +phorum/ + mods/ + htmlpurifier/ + htmlpurifier/ + HTMLPurifier.auto.php + ... - other files + HTMLPurifier/ + +Advanced users: + If you have HTML Purifier installed elsewhere on your server, + all you need is an HTMLPurifier.auto.php file in the library folder which + includes the HTMLPurifier.auto.php file in your install. + +4. MIGRATE +---------- +If you're setting up a new Phorum installation, all you need to do is create +a blank migrate.php file in the htmlpurifier module folder (NOT the library +folder. + +If you have an old Phorum installation and was using BBCode, +copy migrate.bbcode.php to migrate.php. If you were using a different input +format, follow the instructions in migrate.bbcode.php to create your own custom +migrate.php file. + +Your directory structure should now look like this: + +phorum/ + mods/ + htmlpurifier/ + migrate.php + +5. ENABLE +--------- +Navigate to your Phorum admin panel at http://example.com/phorum/admin.php, +click on Global Settings > Modules, scroll to "HTML Purifier Phorum Mod" and +turn it On. + +6. MIGRATE SIGNATURES +--------------------- +If you're setting up a new Phorum installation, skip this step. + +If you allowed your users to make signatures, navigate to the module settings +page of HTML Purifier (Global Settings > Modules > HTML Purifier Phorum Mod > +Configure), type in "yes" in the "Confirm" box, and press "Migrate." + +ONLY DO THIS ONCE! BE SURE TO BACK UP YOUR DATABASE! + +7. CONFIGURE +------------ +Configure using Edit settings. See that page for more information. + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/README b/library/ezyang/htmlpurifier/plugins/phorum/README new file mode 100644 index 0000000000..0524ed39db --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/README @@ -0,0 +1,45 @@ + +HTML Purifier Phorum Mod - Filter your HTML the Standards-Compliant Way! + +This Phorum mod enables HTML posting on Phorum. Under normal circumstances, +this would cause a huge security risk, but because we are running +HTML through HTML Purifier, output is guaranteed to be XSS free and +standards-compliant. + +This mod requires HTML input, and previous markup languages need to be +converted accordingly. Thus, it is vital that you create a 'migrate.php' +file that works with your installation. If you're using the built-in +BBCode formatting, simply move migrate.bbcode.php to that place; for +other markup languages, consult said file for instructions on how +to adapt it to your needs. + + -- NOTE ------------------------------------------------- + You can also run this module in parallel with another + formatting module; this module attempts to place itself + at the end of the filtering chain. However, if any + previous modules produce insecure HTML (for instance, + a JavaScript email obfuscator) they will get cleaned. + +This module will not work if 'migrate.php' is not created, and an improperly +made migration file may *CORRUPT* Phorum, so please take your time to +do this correctly. It should go without saying to *BACKUP YOUR DATABASE* +before attempting anything here. If no migration is necessary, you can +simply create a blank migrate.php file. HTML Purifier is smart and will +not re-migrate already processed messages. However, the original code +is irretrievably lost (we may change this in the future.) + +This module will not automatically migrate user signatures, because this +process may take a long time. After installing the HTML Purifier module and +then configuring 'migrate.php', navigate to Settings and click 'Migrate +Signatures' to migrate all user signatures to HTML. + +All of HTML Purifier's usual functions are configurable via the mod settings +page. If you require custom configuration, create config.php file in +the mod directory that edits a $config variable. Be sure, also, to +set $PHORUM['mod_htmlpurifier']['wysiwyg'] to TRUE if you are using a +WYSIWYG editor (you can do this through a common hook or the web +configuration form). + +Visit HTML Purifier at <http://htmlpurifier.org/>. + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/config.default.php b/library/ezyang/htmlpurifier/plugins/phorum/config.default.php new file mode 100644 index 0000000000..e047c0b423 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/config.default.php @@ -0,0 +1,57 @@ +<?php + +if(!defined("PHORUM")) exit; + +// default HTML Purifier configuration settings +$config->set('HTML.Allowed', + // alphabetically sorted +'a[href|title] +abbr[title] +acronym[title] +b +blockquote[cite] +br +caption +cite +code +dd +del +dfn +div +dl +dt +em +i +img[src|alt|title|class] +ins +kbd +li +ol +p +pre +s +strike +strong +sub +sup +table +tbody +td +tfoot +th +thead +tr +tt +u +ul +var'); +$config->set('AutoFormat.AutoParagraph', true); +$config->set('AutoFormat.Linkify', true); +$config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); +$config->set('Core.AggressivelyFixLt', true); +$config->set('Core.Encoding', $GLOBALS['PHORUM']['DATA']['CHARSET']); // we'll change this eventually +if (strtolower($GLOBALS['PHORUM']['DATA']['CHARSET']) !== 'utf-8') { + $config->set('Core.EscapeNonASCIICharacters', true); +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php b/library/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php new file mode 100644 index 0000000000..f66d8c36cd --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php @@ -0,0 +1,316 @@ +<?php + +/** + * HTML Purifier Phorum Mod. Filter your HTML the Standards-Compliant Way! + * + * This Phorum mod enables users to post raw HTML into Phorum. But never + * fear: with the help of HTML Purifier, this HTML will be beat into + * de-XSSed and standards-compliant form, safe for general consumption. + * It is not recommended, but possible to run this mod in parallel + * with other formatters (in short, please DISABLE the BBcode mod). + * + * For help migrating from your previous markup language to pure HTML + * please check the migrate.bbcode.php file. + * + * If you'd like to use this with a WYSIWYG editor, make sure that + * editor sets $PHORUM['mod_htmlpurifier']['wysiwyg'] to true. Otherwise, + * administrators who need to edit other people's comments may be at + * risk for some nasty attacks. + * + * Tested with Phorum 5.2.11. + */ + +// Note: Cache data is base64 encoded because Phorum insists on flinging +// to the user and expecting it to come back unharmed, newlines and +// all, which ain't happening. It's slower, it takes up more space, but +// at least it won't get mutilated + +/** + * Purifies a data array + */ +function phorum_htmlpurifier_format($data) +{ + $PHORUM = $GLOBALS["PHORUM"]; + + $purifier =& HTMLPurifier::getInstance(); + $cache_serial = $PHORUM['mod_htmlpurifier']['body_cache_serial']; + + foreach($data as $message_id => $message){ + if(isset($message['body'])) { + + if ($message_id) { + // we're dealing with a real message, not a fake, so + // there a number of shortcuts that can be taken + + if (isset($message['meta']['htmlpurifier_light'])) { + // format hook was called outside of Phorum's normal + // functions, do the abridged purification + $data[$message_id]['body'] = $purifier->purify($message['body']); + continue; + } + + if (!empty($PHORUM['args']['purge'])) { + // purge the cache, must be below the following if + unset($message['meta']['body_cache']); + } + + if ( + isset($message['meta']['body_cache']) && + isset($message['meta']['body_cache_serial']) && + $message['meta']['body_cache_serial'] == $cache_serial + ) { + // cached version is present, bail out early + $data[$message_id]['body'] = base64_decode($message['meta']['body_cache']); + continue; + } + } + + // migration might edit this array, that's why it's defined + // so early + $updated_message = array(); + + // create the $body variable + if ( + $message_id && // message must be real to migrate + !isset($message['meta']['body_cache_serial']) + ) { + // perform migration + $fake_data = array(); + list($signature, $edit_message) = phorum_htmlpurifier_remove_sig_and_editmessage($message); + $fake_data[$message_id] = $message; + $fake_data = phorum_htmlpurifier_migrate($fake_data); + $body = $fake_data[$message_id]['body']; + $body = str_replace("<phorum break>\n", "\n", $body); + $updated_message['body'] = $body; // save it in + $body .= $signature . $edit_message; // add it back in + } else { + // reverse Phorum's pre-processing + $body = $message['body']; + // order is important + $body = str_replace("<phorum break>\n", "\n", $body); + $body = str_replace(array('<','>','&', '"'), array('<','>','&','"'), $body); + if (!$message_id && defined('PHORUM_CONTROL_CENTER')) { + // we're in control.php, so it was double-escaped + $body = str_replace(array('<','>','&', '"'), array('<','>','&','"'), $body); + } + } + + $body = $purifier->purify($body); + + // dynamically update the cache (MUST BE DONE HERE!) + // this is inefficient because it's one db call per + // cache miss, but once the cache is in place things are + // a lot zippier. + + if ($message_id) { // make sure it's not a fake id + $updated_message['meta'] = $message['meta']; + $updated_message['meta']['body_cache'] = base64_encode($body); + $updated_message['meta']['body_cache_serial'] = $cache_serial; + phorum_db_update_message($message_id, $updated_message); + } + + // must not get overloaded until after we cache it, otherwise + // we'll inadvertently change the original text + $data[$message_id]['body'] = $body; + + } + } + + return $data; +} + +// ----------------------------------------------------------------------- +// This is fragile code, copied from read.php:596 (Phorum 5.2.6). Please +// keep this code in-sync with Phorum + +/** + * Generates a signature based on a message array + */ +function phorum_htmlpurifier_generate_sig($row) +{ + $phorum_sig = ''; + if(isset($row["user"]["signature"]) + && isset($row['meta']['show_signature']) && $row['meta']['show_signature']==1){ + $phorum_sig=trim($row["user"]["signature"]); + if(!empty($phorum_sig)){ + $phorum_sig="\n\n$phorum_sig"; + } + } + return $phorum_sig; +} + +/** + * Generates an edit message based on a message array + */ +function phorum_htmlpurifier_generate_editmessage($row) +{ + $PHORUM = $GLOBALS['PHORUM']; + $editmessage = ''; + if(isset($row['meta']['edit_count']) && $row['meta']['edit_count'] > 0) { + $editmessage = str_replace ("%count%", $row['meta']['edit_count'], $PHORUM["DATA"]["LANG"]["EditedMessage"]); + $editmessage = str_replace ("%lastedit%", phorum_date($PHORUM["short_date_time"],$row['meta']['edit_date']), $editmessage); + $editmessage = str_replace ("%lastuser%", $row['meta']['edit_username'], $editmessage); + $editmessage = "\n\n\n\n$editmessage"; + } + return $editmessage; +} + +// End fragile code +// ----------------------------------------------------------------------- + +/** + * Removes the signature and edit message from a message + * @param $row Message passed by reference + */ +function phorum_htmlpurifier_remove_sig_and_editmessage(&$row) +{ + $signature = phorum_htmlpurifier_generate_sig($row); + $editmessage = phorum_htmlpurifier_generate_editmessage($row); + $replacements = array(); + // we need to remove add <phorum break> as that is the form these + // extra bits are in. + if ($signature) $replacements[str_replace("\n", "<phorum break>\n", $signature)] = ''; + if ($editmessage) $replacements[str_replace("\n", "<phorum break>\n", $editmessage)] = ''; + $row['body'] = strtr($row['body'], $replacements); + return array($signature, $editmessage); +} + +/** + * Indicate that data is fully HTML and not from migration, invalidate + * previous caches + * @note This function could generate the actual cache entries, but + * since there's data missing that must be deferred to the first read + */ +function phorum_htmlpurifier_posting($message) +{ + $PHORUM = $GLOBALS["PHORUM"]; + unset($message['meta']['body_cache']); // invalidate the cache + $message['meta']['body_cache_serial'] = $PHORUM['mod_htmlpurifier']['body_cache_serial']; + return $message; +} + +/** + * Overload quoting mechanism to prevent default, mail-style quote from happening + */ +function phorum_htmlpurifier_quote($array) +{ + $PHORUM = $GLOBALS["PHORUM"]; + $purifier =& HTMLPurifier::getInstance(); + $text = $purifier->purify($array[1]); + $source = htmlspecialchars($array[0]); + return "<blockquote cite=\"$source\">\n$text\n</blockquote>"; +} + +/** + * Ensure that our format hook is processed last. Also, loads the library. + * @credits <http://secretsauce.phorum.org/snippets/make_bbcode_last_formatter.php.txt> + */ +function phorum_htmlpurifier_common() +{ + require_once(dirname(__FILE__).'/htmlpurifier/HTMLPurifier.auto.php'); + require(dirname(__FILE__).'/init-config.php'); + + $config = phorum_htmlpurifier_get_config(); + HTMLPurifier::getInstance($config); + + // increment revision.txt if you want to invalidate the cache + $GLOBALS['PHORUM']['mod_htmlpurifier']['body_cache_serial'] = $config->getSerial(); + + // load migration + if (file_exists(dirname(__FILE__) . '/migrate.php')) { + include(dirname(__FILE__) . '/migrate.php'); + } else { + echo '<strong>Error:</strong> No migration path specified for HTML Purifier, please check + <tt>modes/htmlpurifier/migrate.bbcode.php</tt> for instructions on + how to migrate from your previous markup language.'; + exit; + } + + if (!function_exists('phorum_htmlpurifier_migrate')) { + // Dummy function + function phorum_htmlpurifier_migrate($data) {return $data;} + } + +} + +/** + * Pre-emptively performs purification if it looks like a WYSIWYG editor + * is being used + */ +function phorum_htmlpurifier_before_editor($message) +{ + if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) { + if (!empty($message['body'])) { + $body = $message['body']; + // de-entity-ize contents + $body = str_replace(array('<','>','&'), array('<','>','&'), $body); + $purifier =& HTMLPurifier::getInstance(); + $body = $purifier->purify($body); + // re-entity-ize contents + $body = htmlspecialchars($body, ENT_QUOTES, $GLOBALS['PHORUM']['DATA']['CHARSET']); + $message['body'] = $body; + } + } + return $message; +} + +function phorum_htmlpurifier_editor_after_subject() +{ + // don't show this message if it's a WYSIWYG editor, since it will + // then be handled automatically + if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) { + $i = $GLOBALS['PHORUM']['DATA']['MODE']; + if ($i == 'quote' || $i == 'edit' || $i == 'moderation') { + ?> + <div> + <p> + <strong>Notice:</strong> HTML has been scrubbed for your safety. + If you would like to see the original, turn off WYSIWYG mode + (consult your administrator for details.) + </p> + </div> + <?php + } + return; + } + if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message'])) return; + ?><div class="htmlpurifier-help"> + <p> + <strong>HTML input</strong> is enabled. Make sure you escape all HTML and + angled brackets with <code>&lt;</code> and <code>&gt;</code>. + </p><?php + $purifier =& HTMLPurifier::getInstance(); + $config = $purifier->config; + if ($config->get('AutoFormat.AutoParagraph')) { + ?><p> + <strong>Auto-paragraphing</strong> is enabled. Double + newlines will be converted to paragraphs; for single + newlines, use the <code>pre</code> tag. + </p><?php + } + $html_definition = $config->getDefinition('HTML'); + $allowed = array(); + foreach ($html_definition->info as $name => $x) $allowed[] = "<code>$name</code>"; + sort($allowed); + $allowed_text = implode(', ', $allowed); + ?><p><strong>Allowed tags:</strong> <?php + echo $allowed_text; + ?>.</p><?php + ?> + </p> + <p> + For inputting literal code such as HTML and PHP for display, use + CDATA tags to auto-escape your angled brackets, and <code>pre</code> + to preserve newlines: + </p> + <pre><pre><![CDATA[ +<em>Place code here</em> +]]></pre></pre> + <p> + Power users, you can hide this notice with: + <pre>.htmlpurifier-help {display:none;}</pre> + </p> + </div><?php +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/info.txt b/library/ezyang/htmlpurifier/plugins/phorum/info.txt new file mode 100644 index 0000000000..7234654901 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/info.txt @@ -0,0 +1,18 @@ +title: HTML Purifier Phorum Mod +desc: This module enables standards-compliant HTML filtering on Phorum. Please check migrate.bbcode.php before enabling this mod. +author: Edward Z. Yang +url: http://htmlpurifier.org/ +version: 4.0.0 + +hook: format|phorum_htmlpurifier_format +hook: quote|phorum_htmlpurifier_quote +hook: posting_custom_action|phorum_htmlpurifier_posting +hook: common|phorum_htmlpurifier_common +hook: before_editor|phorum_htmlpurifier_before_editor +hook: tpl_editor_after_subject|phorum_htmlpurifier_editor_after_subject + +# This module is meant to be a drop-in for bbcode, so make it run last. +priority: run module after * +priority: run hook format after * + + vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/init-config.php b/library/ezyang/htmlpurifier/plugins/phorum/init-config.php new file mode 100644 index 0000000000..e19787b4bc --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/init-config.php @@ -0,0 +1,30 @@ +<?php + +/** + * Initializes the appropriate configuration from either a PHP file + * or a module configuration value + * @return Instance of HTMLPurifier_Config + */ +function phorum_htmlpurifier_get_config($default = false) +{ + global $PHORUM; + $config_exists = phorum_htmlpurifier_config_file_exists(); + if ($default || $config_exists || !isset($PHORUM['mod_htmlpurifier']['config'])) { + $config = HTMLPurifier_Config::createDefault(); + include(dirname(__FILE__) . '/config.default.php'); + if ($config_exists) { + include(dirname(__FILE__) . '/config.php'); + } + unset($PHORUM['mod_htmlpurifier']['config']); // unnecessary + } else { + $config = HTMLPurifier_Config::create($PHORUM['mod_htmlpurifier']['config']); + } + return $config; +} + +function phorum_htmlpurifier_config_file_exists() +{ + return file_exists(dirname(__FILE__) . '/config.php'); +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php b/library/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php new file mode 100644 index 0000000000..0d09194556 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/migrate.bbcode.php @@ -0,0 +1,31 @@ +<?php + +/** + * This file is responsible for migrating from a specific markup language + * like BBCode or Markdown to HTML. WARNING: THIS PROCESS IS NOT REVERSIBLE + * + * Copy this file to 'migrate.php' and it will automatically work for + * BBCode; you may need to tweak this a little to get it to work for other + * languages (usually, just replace the include name and the function name). + * + * If you do NOT want to have any migration performed (for instance, you + * are installing the module on a new forum with no posts), simply remove + * phorum_htmlpurifier_migrate() function. You still need migrate.php + * present, otherwise the module won't work. This ensures that the user + * explicitly says, "No, I do not need to migrate." + */ + +if(!defined("PHORUM")) exit; + +require_once(dirname(__FILE__) . "/../bbcode/bbcode.php"); + +/** + * 'format' hook style function that will be called to convert + * legacy markup into HTML. + */ +function phorum_htmlpurifier_migrate($data) +{ + return phorum_mod_bbcode_format($data); // bbcode's 'format' hook +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings.php b/library/ezyang/htmlpurifier/plugins/phorum/settings.php new file mode 100644 index 0000000000..8158f02823 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/settings.php @@ -0,0 +1,64 @@ +<?php + +// based off of BBCode's settings file + +/** + * HTML Purifier Phorum mod settings configuration. This provides + * a convenient web-interface for editing the most common HTML Purifier + * configuration directives. You can also specify custom configuration + * by creating a 'config.php' file. + */ + +if(!defined("PHORUM_ADMIN")) exit; + +// error reporting is good! +error_reporting(E_ALL ^ E_NOTICE); + +// load library and other paraphenalia +require_once './include/admin/PhorumInputForm.php'; +require_once (dirname(__FILE__) . '/htmlpurifier/HTMLPurifier.auto.php'); +require_once (dirname(__FILE__) . '/init-config.php'); +require_once (dirname(__FILE__) . '/settings/migrate-sigs-form.php'); +require_once (dirname(__FILE__) . '/settings/migrate-sigs.php'); +require_once (dirname(__FILE__) . '/settings/form.php'); +require_once (dirname(__FILE__) . '/settings/save.php'); + +// define friendly configuration directives. you can expand this array +// to get more web-definable directives +$PHORUM['mod_htmlpurifier']['directives'] = array( + 'URI.Host', // auto-detectable + 'URI.DisableExternal', + 'URI.DisableExternalResources', + 'URI.DisableResources', + 'URI.Munge', + 'URI.HostBlacklist', + 'URI.Disable', + 'HTML.TidyLevel', + 'HTML.Doctype', // auto-detectable + 'HTML.Allowed', + 'AutoFormat', + '-AutoFormat.Custom', + 'AutoFormatParam', + 'Output.TidyFormat', +); + +// lower this setting if you're getting time outs/out of memory +$PHORUM['mod_htmlpurifier']['migrate-sigs-increment'] = 100; + +if (isset($_POST['reset'])) { + unset($PHORUM['mod_htmlpurifier']['config']); +} + +if ($offset = phorum_htmlpurifier_migrate_sigs_check()) { + // migrate signatures + phorum_htmlpurifier_migrate_sigs($offset); +} elseif(!empty($_POST)){ + // save settings + phorum_htmlpurifier_save_settings(); +} + +phorum_htmlpurifier_show_migrate_sigs_form(); +echo '<br />'; +phorum_htmlpurifier_show_form(); + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/form.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/form.php new file mode 100644 index 0000000000..9b6ad5f39e --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/settings/form.php @@ -0,0 +1,95 @@ +<?php + +function phorum_htmlpurifier_show_form() +{ + if (phorum_htmlpurifier_config_file_exists()) { + phorum_htmlpurifier_show_config_info(); + return; + } + + global $PHORUM; + + $config = phorum_htmlpurifier_get_config(); + + $frm = new PhorumInputForm ("", "post", "Save"); + $frm->hidden("module", "modsettings"); + $frm->hidden("mod", "htmlpurifier"); // this is the directory name that the Settings file lives in + + if (!empty($error)){ + echo "$error<br />"; + } + + $frm->addbreak("Edit settings for the HTML Purifier module"); + + $frm->addMessage('<p>The box below sets <code>$PHORUM[\'mod_htmlpurifier\'][\'wysiwyg\']</code>. + When checked, contents sent for edit are now purified and the + informative message is disabled. If your WYSIWYG editor is disabled for + admin edits, you can safely keep this unchecked.</p>'); + $frm->addRow('Use WYSIWYG?', $frm->checkbox('wysiwyg', '1', '', $PHORUM['mod_htmlpurifier']['wysiwyg'])); + + $frm->addMessage('<p>The box below sets <code>$PHORUM[\'mod_htmlpurifier\'][\'suppress_message\']</code>, + which removes the big how-to use + HTML Purifier message.</p>'); + $frm->addRow('Suppress information?', $frm->checkbox('suppress_message', '1', '', $PHORUM['mod_htmlpurifier']['suppress_message'])); + + $frm->addMessage('<p>Click on directive links to read what each option does + (links do not open in new windows).</p> + <p>For more flexibility (for instance, you want to edit the full + range of configuration directives), you can create a <tt>config.php</tt> + file in your <tt>mods/htmlpurifier/</tt> directory. Doing so will, + however, make the web configuration interface unavailable.</p>'); + + require_once 'HTMLPurifier/Printer/ConfigForm.php'; + $htmlpurifier_form = new HTMLPurifier_Printer_ConfigForm('config', 'http://htmlpurifier.org/live/configdoc/plain.html#%s'); + $htmlpurifier_form->setTextareaDimensions(23, 7); // widen a little, since we have space + + $frm->addMessage($htmlpurifier_form->render( + $config, $PHORUM['mod_htmlpurifier']['directives'], false)); + + $frm->addMessage("<strong>Warning: Changing HTML Purifier's configuration will invalidate + the cache. Expect to see a flurry of database activity after you change + any of these settings.</strong>"); + + $frm->addrow('Reset to defaults:', $frm->checkbox("reset", "1", "", false)); + + // hack to include extra styling + echo '<style type="text/css">' . $htmlpurifier_form->getCSS() . ' + .hp-config {margin-left:auto;margin-right:auto;} + </style>'; + $js = $htmlpurifier_form->getJavaScript(); + echo '<script type="text/javascript">'."<!--\n$js\n//-->".'</script>'; + + $frm->show(); +} + +function phorum_htmlpurifier_show_config_info() +{ + global $PHORUM; + + // update mod_htmlpurifier for housekeeping + phorum_htmlpurifier_commit_settings(); + + // politely tell user how to edit settings manually +?> + <div class="input-form-td-break">How to edit settings for HTML Purifier module</div> + <p> + A <tt>config.php</tt> file exists in your <tt>mods/htmlpurifier/</tt> + directory. This file contains your custom configuration: in order to + change it, please navigate to that file and edit it accordingly. + You can also set <code>$GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg']</code> + or <code>$GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message']</code> + </p> + <p> + To use the web interface, delete <tt>config.php</tt> (or rename it to + <tt>config.php.bak</tt>). + </p> + <p> + <strong>Warning: Changing HTML Purifier's configuration will invalidate + the cache. Expect to see a flurry of database activity after you change + any of these settings.</strong> + </p> +<?php + +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php new file mode 100644 index 0000000000..abea3b51d7 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs-form.php @@ -0,0 +1,22 @@ +<?php + +function phorum_htmlpurifier_show_migrate_sigs_form() +{ + $frm = new PhorumInputForm ('', "post", "Migrate"); + $frm->hidden("module", "modsettings"); + $frm->hidden("mod", "htmlpurifier"); + $frm->hidden("migrate-sigs", "1"); + $frm->addbreak("Migrate user signatures to HTML"); + $frm->addMessage('This operation will migrate your users signatures + to HTML. <strong>This process is irreversible and must only be performed once.</strong> + Type in yes in the confirmation field to migrate.'); + if (!file_exists(dirname(__FILE__) . '/../migrate.php')) { + $frm->addMessage('Migration file does not exist, cannot migrate signatures. + Please check <tt>migrate.bbcode.php</tt> on how to create an appropriate file.'); + } else { + $frm->addrow('Confirm:', $frm->text_box("confirmation", "")); + } + $frm->show(); +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php new file mode 100644 index 0000000000..5ea9cd0b81 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php @@ -0,0 +1,79 @@ +<?php + +function phorum_htmlpurifier_migrate_sigs_check() +{ + global $PHORUM; + $offset = 0; + if (!empty($_POST['migrate-sigs'])) { + if (!isset($_POST['confirmation']) || strtolower($_POST['confirmation']) !== 'yes') { + echo 'Invalid confirmation code.'; + exit; + } + $PHORUM['mod_htmlpurifier']['migrate-sigs'] = true; + phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"])); + $offset = 1; + } elseif (!empty($_GET['migrate-sigs']) && $PHORUM['mod_htmlpurifier']['migrate-sigs']) { + $offset = (int) $_GET['migrate-sigs']; + } + return $offset; +} + +function phorum_htmlpurifier_migrate_sigs($offset) +{ + global $PHORUM; + + if(!$offset) return; // bail out quick if $offset == 0 + + // theoretically, we could get rid of this multi-request + // doo-hickery if safe mode is off + @set_time_limit(0); // attempt to let this run + $increment = $PHORUM['mod_htmlpurifier']['migrate-sigs-increment']; + + require_once(dirname(__FILE__) . '/../migrate.php'); + // migrate signatures + // do this in batches so we don't run out of time/space + $end = $offset + $increment; + $user_ids = array(); + for ($i = $offset; $i < $end; $i++) { + $user_ids[] = $i; + } + $userinfos = phorum_db_user_get_fields($user_ids, 'signature'); + foreach ($userinfos as $i => $user) { + if (empty($user['signature'])) continue; + $sig = $user['signature']; + // perform standard Phorum processing on the sig + $sig = str_replace(array("&","<",">"), array("&","<",">"), $sig); + $sig = preg_replace("/<((http|https|ftp):\/\/[a-z0-9;\/\?:@=\&\$\-_\.\+!*'\(\),~%]+?)>/i", "$1", $sig); + // prepare fake data to pass to migration function + $fake_data = array(array("author"=>"", "email"=>"", "subject"=>"", 'body' => $sig)); + list($fake_message) = phorum_htmlpurifier_migrate($fake_data); + $user['signature'] = $fake_message['body']; + if (!phorum_api_user_save($user)) { + exit('Error while saving user data'); + } + } + unset($userinfos); // free up memory + + // query for highest ID in database + $type = $PHORUM['DBCONFIG']['type']; + $sql = "select MAX(user_id) from {$PHORUM['user_table']}"; + $row = phorum_db_interact(DB_RETURN_ROW, $sql); + $top_id = (int) $row[0]; + + $offset += $increment; + if ($offset > $top_id) { // test for end condition + echo 'Migration finished'; + $PHORUM['mod_htmlpurifier']['migrate-sigs'] = false; + phorum_htmlpurifier_commit_settings(); + return true; + } + $host = $_SERVER['HTTP_HOST']; + $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\'); + $extra = 'admin.php?module=modsettings&mod=htmlpurifier&migrate-sigs=' . $offset; + // relies on output buffering to work + header("Location: http://$host$uri/$extra"); + exit; + +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/plugins/phorum/settings/save.php b/library/ezyang/htmlpurifier/plugins/phorum/settings/save.php new file mode 100644 index 0000000000..2aefaf83a0 --- /dev/null +++ b/library/ezyang/htmlpurifier/plugins/phorum/settings/save.php @@ -0,0 +1,29 @@ +<?php + +function phorum_htmlpurifier_save_settings() +{ + global $PHORUM; + if (phorum_htmlpurifier_config_file_exists()) { + echo "Cannot update settings, <code>mods/htmlpurifier/config.php</code> already exists. To change + settings, edit that file. To use the web form, delete that file.<br />"; + } else { + $config = phorum_htmlpurifier_get_config(true); + if (!isset($_POST['reset'])) $config->mergeArrayFromForm($_POST, 'config', $PHORUM['mod_htmlpurifier']['directives']); + $PHORUM['mod_htmlpurifier']['config'] = $config->getAll(); + } + $PHORUM['mod_htmlpurifier']['wysiwyg'] = !empty($_POST['wysiwyg']); + $PHORUM['mod_htmlpurifier']['suppress_message'] = !empty($_POST['suppress_message']); + if(!phorum_htmlpurifier_commit_settings()){ + $error="Database error while updating settings."; + } else { + echo "Settings Updated<br />"; + } +} + +function phorum_htmlpurifier_commit_settings() +{ + global $PHORUM; + return phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"])); +} + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/release1-update.php b/library/ezyang/htmlpurifier/release1-update.php new file mode 100644 index 0000000000..834d385676 --- /dev/null +++ b/library/ezyang/htmlpurifier/release1-update.php @@ -0,0 +1,110 @@ +<?php + +// release script +// PHP 5.0 only + +if (php_sapi_name() != 'cli') { + echo 'Release script cannot be called from web-browser.'; + exit; +} + +if (!isset($argv[1])) { + echo +'php release.php [version] + HTML Purifier release script +'; + exit; +} + +$version = trim($argv[1]); + +// Bump version numbers: + +// ...in VERSION +file_put_contents('VERSION', $version); + +// ...in NEWS +if ($is_dev = (strpos($version, 'dev') === false)) { + $date = date('Y-m-d'); + $news_c = str_replace( + $l = "$version, unknown release date", + "$version, released $date", + file_get_contents('NEWS'), + $c + ); + if (!$c) { + echo 'Could not update NEWS, missing ' . $l . PHP_EOL; + exit; + } elseif ($c > 1) { + echo 'More than one release declaration in NEWS replaced' . PHP_EOL; + exit; + } + file_put_contents('NEWS', $news_c); +} + +// ...in Doxyfile +$doxyfile_c = preg_replace( + '/(?<=PROJECT_NUMBER {9}= )[^\s]+/m', // brittle + $version, + file_get_contents('Doxyfile'), + 1, $c +); +if (!$c) { + echo 'Could not update Doxyfile, missing PROJECT_NUMBER.' . PHP_EOL; + exit; +} +file_put_contents('Doxyfile', $doxyfile_c); + +// ...in HTMLPurifier.php +$htmlpurifier_c = file_get_contents('library/HTMLPurifier.php'); +$htmlpurifier_c = preg_replace( + '/HTML Purifier .+? - /', + "HTML Purifier $version - ", + $htmlpurifier_c, + 1, $c +); +if (!$c) { + echo 'Could not update HTMLPurifier.php, missing HTML Purifier [version] header.' . PHP_EOL; + exit; +} +$htmlpurifier_c = preg_replace( + '/public \$version = \'.+?\';/', + "public \$version = '$version';", + $htmlpurifier_c, + 1, $c +); +if (!$c) { + echo 'Could not update HTMLPurifier.php, missing public $version.' . PHP_EOL; + exit; +} +$htmlpurifier_c = preg_replace( + '/const VERSION = \'.+?\';/', + "const VERSION = '$version';", + $htmlpurifier_c, + 1, $c +); +if (!$c) { + echo 'Could not update HTMLPurifier.php, missing const $version.' . PHP_EOL; + exit; +} +file_put_contents('library/HTMLPurifier.php', $htmlpurifier_c); + +$config_c = file_get_contents('library/HTMLPurifier/Config.php'); +$config_c = preg_replace( + '/public \$version = \'.+?\';/', + "public \$version = '$version';", + $config_c, + 1, $c +); +if (!$c) { + echo 'Could not update Config.php, missing public $version.' . PHP_EOL; + exit; +} +file_put_contents('library/HTMLPurifier/Config.php', $config_c); + +passthru('php maintenance/flush.php'); + +if ($is_dev) echo "Review changes, write something in WHATSNEW and FOCUS, and then commit with log 'Release $version.'" . PHP_EOL; +else echo "Numbers updated to dev, no other modifications necessary!"; + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/release2-tag.php b/library/ezyang/htmlpurifier/release2-tag.php new file mode 100644 index 0000000000..25e5300d81 --- /dev/null +++ b/library/ezyang/htmlpurifier/release2-tag.php @@ -0,0 +1,22 @@ +<?php + +// Tags releases + +if (php_sapi_name() != 'cli') { + echo 'Release script cannot be called from web-browser.'; + exit; +} + +require 'svn.php'; + +$svn_info = my_svn_info('.'); + +$version = trim(file_get_contents('VERSION')); + +$trunk_url = $svn_info['Repository Root'] . '/htmlpurifier/trunk'; +$trunk_tag_url = $svn_info['Repository Root'] . '/htmlpurifier/tags/' . $version; + +echo "Tagging trunk to tags/$version..."; +passthru("svn copy --message \"Tag $version release.\" $trunk_url $trunk_tag_url"); + +// vim: et sw=4 sts=4 diff --git a/library/ezyang/htmlpurifier/test-settings.sample.php b/library/ezyang/htmlpurifier/test-settings.sample.php new file mode 100644 index 0000000000..886b974867 --- /dev/null +++ b/library/ezyang/htmlpurifier/test-settings.sample.php @@ -0,0 +1,76 @@ +<?php + +// ATTENTION! DO NOT EDIT THIS FILE! +// This file is necessary to run the unit tests and profiling scripts. +// Please copy it to 'test-settings.php' and make the necessary edits. + +// Note: The only external library you *need* is SimpleTest; everything else +// is optional. + +// We've got a lot of tests, so we recommend turning the limit off. +set_time_limit(0); + +// Turning off output buffering will prevent mysterious errors from core dumps. +$data = @ob_get_clean(); +if ($data !== false && $data !== '') { + echo "Output buffer contains data [".urlencode($data)."]\n"; + exit; +} + +// ----------------------------------------------------------------------------- +// REQUIRED SETTINGS + +// Note on running SimpleTest: +// Because HTML Purifier is PHP5-only and E_STRICT compliant, SimpleTest +// 1.0.1 will not work; you need to run SimpleTest off its trunk using: +// +// $ svn co https://simpletest.svn.sourceforge.net/svnroot/simpletest/simpletest/trunk simpletest +// +// If SimpleTest is borked with HTML Purifier, please contact me or +// the SimpleTest devs; I am a developer for SimpleTest so I should be +// able to quickly assess a fix. SimpleTest's problem is my problem! + +// Where is SimpleTest located? Remember to include a trailing slash! +$simpletest_location = '/path/to/simpletest/'; + +// ----------------------------------------------------------------------------- +// OPTIONAL SETTINGS + +// Note on running PHPT: +// Vanilla PHPT from https://github.com/tswicegood/PHPT_Core should +// work fine on Linux w/o multitest. +// +// To do multitest or Windows testing, you'll need some more +// patches at https://github.com/ezyang/PHPT_Core +// +// I haven't tested the Windows setup in a while so I don't know if +// it still works. + +// Should PHPT tests be enabled? +$GLOBALS['HTMLPurifierTest']['PHPT'] = false; + +// If PHPT isn't in your Path via PEAR, set that here: +// set_include_path('/path/to/phpt/Core/src' . PATH_SEPARATOR . get_include_path()); + +// Where is CSSTidy located? (Include trailing slash. Leave false to disable.) +$csstidy_location = false; + +// For tests/multitest.php, which versions to test? +$versions_to_test = array(); + +// Stable PHP binary to use when invoking maintenance scripts. +$php = 'php'; + +// For tests/multitest.php, what is the multi-version executable? It must +// accept an extra parameter (version number) before all other arguments +$phpv = false; + +// Should PEAR tests be run? If you've got a valid PEAR installation, set this +// to true (or, if it's not in the include path, to its install directory). +$GLOBALS['HTMLPurifierTest']['PEAR'] = false; + +// If PEAR is enabled, what PEAR tests should be run? (Note: you will +// need to ensure these libraries are installed) +$GLOBALS['HTMLPurifierTest']['Net_IDNA2'] = true; + +// vim: et sw=4 sts=4 From c6514d58dbece71f25a44985a41ec18cdd50422d Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Tue, 9 Feb 2016 11:07:37 +0100 Subject: [PATCH 075/273] add composer-derived autoloader --- boot.php | 2 + include/autoloader.php | 63 ++++ include/autoloader/ClassLoader.php | 413 +++++++++++++++++++++ include/autoloader/LICENSE.composer | 19 + include/autoloader/autoload_classmap.php | 9 + include/autoloader/autoload_files.php | 10 + include/autoloader/autoload_namespaces.php | 10 + include/autoloader/autoload_psr4.php | 9 + 8 files changed, 535 insertions(+) create mode 100644 include/autoloader.php create mode 100644 include/autoloader/ClassLoader.php create mode 100644 include/autoloader/LICENSE.composer create mode 100644 include/autoloader/autoload_classmap.php create mode 100644 include/autoloader/autoload_files.php create mode 100644 include/autoloader/autoload_namespaces.php create mode 100644 include/autoloader/autoload_psr4.php diff --git a/boot.php b/boot.php index 4ef30eadac..dd70040c64 100644 --- a/boot.php +++ b/boot.php @@ -17,6 +17,8 @@ * easily as email does today. */ +require_once('include/autoloader.php'); + require_once('include/config.php'); require_once('include/network.php'); require_once('include/plugin.php'); diff --git a/include/autoloader.php b/include/autoloader.php new file mode 100644 index 0000000000..b3b83cbc7c --- /dev/null +++ b/include/autoloader.php @@ -0,0 +1,63 @@ +<?php + +class FriendicaAutoloaderInit +{ + private static $loader; + + public static function loadClassLoader($class) + { + if ('Composer\Autoload\ClassLoader' === $class) { + require __DIR__ . '/autoloader/ClassLoader.php'; + } + } + + public static function getLoader() + { + if (null !== self::$loader) { + return self::$loader; + } + + spl_autoload_register(array('FriendicaAutoloaderInit', 'loadClassLoader'), true, true); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + spl_autoload_unregister(array('FriendicaAutoloaderInit', 'loadClassLoader')); + + // library + $map = require __DIR__ . '/autoloader/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoloader/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoloader/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + $includeFiles = require __DIR__ . '/autoloader/autoload_files.php'; + foreach ($includeFiles as $fileIdentifier => $file) { + friendicaRequire($fileIdentifier, $file); + } + + + return $loader; + } +} + +function friendicaRequire($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} + + + +return FriendicaAutoloaderInit::getLoader(); diff --git a/include/autoloader/ClassLoader.php b/include/autoloader/ClassLoader.php new file mode 100644 index 0000000000..d916d802fe --- /dev/null +++ b/include/autoloader/ClassLoader.php @@ -0,0 +1,413 @@ +<?php + +/* + * This file is part of Composer. + * + * (c) Nils Adermann <naderman@naderman.de> + * Jordi Boggiano <j.boggiano@seld.be> + * + * For the full copyright and license information, please view the LICENSE.composer + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Jordi Boggiano <j.boggiano@seld.be> + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/include/autoloader/LICENSE.composer b/include/autoloader/LICENSE.composer new file mode 100644 index 0000000000..b365b1f5a7 --- /dev/null +++ b/include/autoloader/LICENSE.composer @@ -0,0 +1,19 @@ +Copyright (c) 2015 Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the Software), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, andor sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/include/autoloader/autoload_classmap.php b/include/autoloader/autoload_classmap.php new file mode 100644 index 0000000000..3efd09fc69 --- /dev/null +++ b/include/autoloader/autoload_classmap.php @@ -0,0 +1,9 @@ +<?php + +// autoload_classmap.php @generated by Composer + +$vendorDir = dirname(dirname(dirname(__FILE__)))."/library"; +$baseDir = dirname($vendorDir); + +return array( +); diff --git a/include/autoloader/autoload_files.php b/include/autoloader/autoload_files.php new file mode 100644 index 0000000000..859135cc48 --- /dev/null +++ b/include/autoloader/autoload_files.php @@ -0,0 +1,10 @@ +<?php + +// autoload_files.php @generated by Composer + +$vendorDir = dirname(dirname(dirname(__FILE__)))."/library"; +$baseDir = dirname($vendorDir); + +return array( + '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', +); diff --git a/include/autoloader/autoload_namespaces.php b/include/autoloader/autoload_namespaces.php new file mode 100644 index 0000000000..315a349310 --- /dev/null +++ b/include/autoloader/autoload_namespaces.php @@ -0,0 +1,10 @@ +<?php + +// autoload_namespaces.php @generated by Composer + +$vendorDir = dirname(dirname(dirname(__FILE__)))."/library"; +$baseDir = dirname($vendorDir); + +return array( + 'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'), +); diff --git a/include/autoloader/autoload_psr4.php b/include/autoloader/autoload_psr4.php new file mode 100644 index 0000000000..fe93afea21 --- /dev/null +++ b/include/autoloader/autoload_psr4.php @@ -0,0 +1,9 @@ +<?php + +// autoload_psr4.php @generated by Composer + +$vendorDir = dirname(dirname(dirname(__FILE__)))."/library"; +$baseDir = dirname($vendorDir); + +return array( +); From aec8a1883e1bf0bb1ea6c2b11a1ff64796425788 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Tue, 9 Feb 2016 11:08:26 +0100 Subject: [PATCH 076/273] let autoloader include files for HTMLPurifier --- include/api.php | 5 ----- include/dfrn.php | 1 - include/event.php | 1 - 3 files changed, 7 deletions(-) diff --git a/include/api.php b/include/api.php index 4d206da28e..bddd4cfc7d 100644 --- a/include/api.php +++ b/include/api.php @@ -781,8 +781,6 @@ if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) { - require_once('library/HTMLPurifier.auto.php'); - $txt = html2bb_video($txt); $config = HTMLPurifier_Config::createDefault(); $config->set('Cache.DefinitionImpl', null); @@ -822,9 +820,6 @@ if(requestdata('htmlstatus')) { $txt = requestdata('htmlstatus'); if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) { - - require_once('library/HTMLPurifier.auto.php'); - $txt = html2bb_video($txt); $config = HTMLPurifier_Config::createDefault(); diff --git a/include/dfrn.php b/include/dfrn.php index c4999a08e1..38c7b425fa 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -18,7 +18,6 @@ require_once("include/event.php"); require_once("include/text.php"); require_once("include/oembed.php"); require_once("include/html2bbcode.php"); -require_once("library/HTMLPurifier.auto.php"); /** * @brief This class contain functions to create and send DFRN XML files diff --git a/include/event.php b/include/event.php index 13c414c9e3..a9f054fc2e 100644 --- a/include/event.php +++ b/include/event.php @@ -76,7 +76,6 @@ function format_event_html($ev, $simple = false) { function parse_event($h) { require_once('include/Scrape.php'); - require_once('library/HTMLPurifier.auto.php'); require_once('include/html2bbcode'); $h = '<html><body>' . $h . '</body></html>'; From d2a9aa19721568bf28e844e1ca2f596d7dcca240 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Tue, 9 Feb 2016 11:10:00 +0100 Subject: [PATCH 077/273] add some doxygen comments --- include/autoloader.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/autoloader.php b/include/autoloader.php index b3b83cbc7c..6caa082915 100644 --- a/include/autoloader.php +++ b/include/autoloader.php @@ -1,5 +1,11 @@ <?php +/** + * @file include/autoloader.php + */ +/** + * @brief composer-derived autoloader init + **/ class FriendicaAutoloaderInit { private static $loader; From 4d7a6aaed240aa04e969aa33e6fd6ed6b6cb6bc8 Mon Sep 17 00:00:00 2001 From: Andrej Stieben <e-mail@stieben.de> Date: Tue, 9 Feb 2016 15:54:35 +0100 Subject: [PATCH 078/273] Moved div#pause from index.php to view/default.php This closes #2320. --- index.php | 9 --------- view/default.php | 5 +++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/index.php b/index.php index 2b1053cc1b..ca93eb61e7 100644 --- a/index.php +++ b/index.php @@ -416,15 +416,6 @@ if(x($_SESSION,'sysmsg_info')) { call_hooks('page_end', $a->page['content']); -/** - * - * Add a place for the pause/resume Ajax indicator - * - */ - -$a->page['content'] .= '<div id="pause"></div>'; - - /** * * Add the navigation (menu) template diff --git a/view/default.php b/view/default.php index 78ca97ac94..121d5212c6 100644 --- a/view/default.php +++ b/view/default.php @@ -8,11 +8,12 @@ <body> <?php if(x($page,'nav')) echo $page['nav']; ?> <aside><?php if(x($page,'aside')) echo $page['aside']; ?></aside> - <section><?php if(x($page,'content')) echo $page['content']; ?> + <section> + <?php if(x($page,'content')) echo $page['content']; ?> + <div id="pause"></div> <!-- The pause/resume Ajax indicator --> <div id="page-footer"></div> </section> <right_aside><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></right_aside> <footer><?php if(x($page,'footer')) echo $page['footer']; ?></footer> </body> </html> - From e104c5819123cc9e836e8c6bd5f253bc87da341d Mon Sep 17 00:00:00 2001 From: Andrej Stieben <e-mail@stieben.de> Date: Tue, 9 Feb 2016 16:41:19 +0100 Subject: [PATCH 079/273] Added div#pause to themes' default.php As requested by @tobiasd and @rabuzarus. --- view/theme/frost-mobile/default.php | 5 +++-- view/theme/frost/default.php | 5 +++-- view/theme/smoothly/default.php | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/view/theme/frost-mobile/default.php b/view/theme/frost-mobile/default.php index ad464760f7..3a424ad5f4 100644 --- a/view/theme/frost-mobile/default.php +++ b/view/theme/frost-mobile/default.php @@ -28,7 +28,9 @@ <!-- <div class='main-content-container'>--> <div class='section-wrapper'> <?php if( ($a->module === 'settings' || $a->module === 'message' || $a->module === 'profile') && x($page,'aside')) echo $page['aside']; ?> - <section><?php if(x($page,'content')) echo $page['content']; ?> + <section> + <?php if(x($page,'content')) echo $page['content']; ?> + <div id="pause"></div> <!-- The pause/resume Ajax indicator --> <div id="page-footer"></div> </section> </div> @@ -41,4 +43,3 @@ <?php if(x($page,'end')) echo $page['end']; ?> </body> </html> - diff --git a/view/theme/frost/default.php b/view/theme/frost/default.php index 95a9b1e8c7..c379955f79 100644 --- a/view/theme/frost/default.php +++ b/view/theme/frost/default.php @@ -28,7 +28,9 @@ <!--<div class='main-content-loading'><img src="/view/theme/frost/images/ajax-loader.gif" alt="Please wait..."></div>--> <div class='main-content-container'> <aside><?php if(x($page,'aside')) echo $page['aside']; ?></aside> - <section><?php if(x($page,'content')) echo $page['content']; ?> + <section> + <?php if(x($page,'content')) echo $page['content']; ?> + <div id="pause"></div> <!-- The pause/resume Ajax indicator --> <div id="page-footer"></div> </section> <right_aside><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></right_aside> @@ -39,4 +41,3 @@ <?php if(x($page,'end')) echo $page['end']; ?> </body> </html> - diff --git a/view/theme/smoothly/default.php b/view/theme/smoothly/default.php index 9ac49820aa..405e1cad34 100644 --- a/view/theme/smoothly/default.php +++ b/view/theme/smoothly/default.php @@ -19,7 +19,9 @@ <aside><?php if(x($page,'aside')) echo $page['aside']; ?></aside> - <section><?php if(x($page,'content')) echo $page['content']; ?> + <section> + <?php if(x($page,'content')) echo $page['content']; ?> + <div id="pause"></div> <!-- The pause/resume Ajax indicator --> <div id="page-footer"></div> </section> @@ -41,4 +43,3 @@ <?php if (x($page, 'bottom')) echo $page['bottom']; ?> </body> </html> - From 211be5bfc521285f83c9b85211721d7815fa5689 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 9 Feb 2016 23:28:33 +0100 Subject: [PATCH 080/273] Poller: The maximum number of connections can now be configured --- include/dfrn.php | 4 +++- include/poller.php | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index c4999a08e1..6fa47af052 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -1252,8 +1252,10 @@ class dfrn { // Update check for this field has to be done differently $datefields = array("name-date", "uri-date"); foreach ($datefields AS $field) - if (strtotime($contact[$field]) > strtotime($r[0][$field])) + if (strtotime($contact[$field]) > strtotime($r[0][$field])) { + logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG); $update = true; + } foreach ($fields AS $field => $data) if ($contact[$field] != $r[0][$field]) { diff --git a/include/poller.php b/include/poller.php index 712f6d5788..03f0307712 100644 --- a/include/poller.php +++ b/include/poller.php @@ -130,9 +130,14 @@ function poller_max_connections_reached() { if (!$r) return false; - $max = intval($r[0]["Value"]); - if ($max == 0) - return false; + // Fetch the max value from the config. This is needed when the system cannot detect the correct value by itself. + $max = get_config("system", "max_connections"); + + if ($max == 0) { + $max = intval($r[0]["Value"]); + if ($max == 0) + return false; + } $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'"); if (!$r) From 902820b30d90c3b0930256388219a7e2d9b8cea6 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 10 Feb 2016 07:56:49 +0100 Subject: [PATCH 081/273] Added documentation --- doc/htconfig.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/htconfig.md b/doc/htconfig.md index 4764c287c8..f9c92bfa08 100644 --- a/doc/htconfig.md +++ b/doc/htconfig.md @@ -34,6 +34,7 @@ line to your .htconfig.php: * like_no_comment (Boolean) - Don't update the "commented" value of an item when it is liked. * local_block (Boolean) - Used in conjunction with "block_public". * local_search (Boolean) - Blocks the search for not logged in users to prevent crawlers from blocking your system. +* max_connections - The poller process isn't started when 3/4 of the possible database connections are used. When the system can't detect the maximum numbers of connection then this value can be used. * max_contact_queue - Default value is 500. * max_batch_queue - Default value is 1000. * no_oembed (Boolean) - Don't use OEmbed to fetch more information about a link. From 20d7d18379f3d0e8c9928995588ce716033bc07f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 10 Feb 2016 23:39:18 +0100 Subject: [PATCH 082/273] Bugfix: The "keywords" field in the gcontact table wasn't set correctly --- include/socgraph.php | 21 +++++++++++---------- mod/pubsub.php | 4 ++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/socgraph.php b/include/socgraph.php index c545343393..3b8e9140f8 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -139,15 +139,16 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) { poco_check($profile_url, $name, $network, $profile_photo, $about, $location, $gender, $keywords, $connect_url, $updated, $generation, $cid, $uid, $zcid); // Update the Friendica contacts. Diaspora is doing it via a message. (See include/diaspora.php) - if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != "")) - q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' - WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'", - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - dbesc(normalise_link($profile_url)), - dbesc(NETWORK_DFRN)); + // Deactivated because we now update Friendica contacts in dfrn.php + //if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != "")) + // q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' + // WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'", + // dbesc($location), + // dbesc($about), + // dbesc($keywords), + // dbesc($gender), + // dbesc(normalise_link($profile_url)), + // dbesc(NETWORK_DFRN)); } logger("poco_load: loaded $total entries",LOGGER_DEBUG); @@ -1554,7 +1555,7 @@ function update_gcontact($contact) { if ($update) { q("UPDATE `gcontact` SET `photo` = '%s', `name` = '%s', `nick` = '%s', `addr` = '%s', `network` = '%s', - `birthday` = '%s', `gender` = '%s', `keywords` = %d, `hide` = %d, `nsfw` = %d, + `birthday` = '%s', `gender` = '%s', `keywords` = '%s', `hide` = %d, `nsfw` = %d, `alias` = '%s', `notify` = '%s', `url` = '%s', `location` = '%s', `about` = '%s', `generation` = %d, `updated` = '%s', `server_url` = '%s', `connect` = '%s' diff --git a/mod/pubsub.php b/mod/pubsub.php index beb73b4e2c..6053ee2fbe 100644 --- a/mod/pubsub.php +++ b/mod/pubsub.php @@ -122,8 +122,8 @@ function pubsub_post(&$a) { $importer = $r[0]; - $r = q("SELECT * FROM `contact` WHERE `subhub` = 1 AND `id` = %d AND `uid` = %d - AND ( `rel` = %d OR `rel` = %d OR network = '%s' ) AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", + $r = q("SELECT * FROM `contact` WHERE `subhub` AND `id` = %d AND `uid` = %d + AND (`rel` = %d OR `rel` = %d OR network = '%s') AND NOT `blocked` LIMIT 1", intval($contact_id), intval($importer['uid']), intval(CONTACT_IS_SHARING), From e20aad43477a4a4794b34e72b7a4106ac0dbd836 Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Thu, 11 Feb 2016 09:05:00 +0100 Subject: [PATCH 083/273] fix missing html2plain() function declaration --- include/NotificationsManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/include/NotificationsManager.php b/include/NotificationsManager.php index c99d00742c..5f8211eb87 100644 --- a/include/NotificationsManager.php +++ b/include/NotificationsManager.php @@ -2,6 +2,7 @@ /** * @file include/NotificationsManager.php */ +require_once('include/html2plain.php'); require_once("include/datetime.php"); require_once("include/bbcode.php"); From 4af77b60cebc2e3bdf1a393fde0d2c2672acecec Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 11 Feb 2016 11:33:45 +0100 Subject: [PATCH 084/273] Poller: Use the processlist when the number of maximum database connections was provided manually --- include/poller.php | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/include/poller.php b/include/poller.php index 03f0307712..9976e76db2 100644 --- a/include/poller.php +++ b/include/poller.php @@ -131,22 +131,29 @@ function poller_max_connections_reached() { return false; // Fetch the max value from the config. This is needed when the system cannot detect the correct value by itself. + // In that case we use the processlist to determine the current number of connections $max = get_config("system", "max_connections"); if ($max == 0) { $max = intval($r[0]["Value"]); if ($max == 0) return false; + + $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'"); + if (!$r) + return false; + + $connected = intval($r[0]["Value"]); + if ($connected == 0) + return false; + } else { + $r = q("SHOW PROCESSLIST"); + if (!$r) + return false; + + $connected = count($r); } - $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'"); - if (!$r) - return false; - - $connected = intval($r[0]["Value"]); - if ($connected == 0) - return false; - $level = $connected / $max; logger("Connection usage: ".$connected."/".$max, LOGGER_DEBUG); From 5027fbf733d362e3b2c5a4fdf63ebc746a065945 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 11 Feb 2016 21:39:34 +0100 Subject: [PATCH 085/273] Poller: Now the user limits can be detected automatically as well --- include/poller.php | 56 +++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/include/poller.php b/include/poller.php index 9976e76db2..d68e8081ca 100644 --- a/include/poller.php +++ b/include/poller.php @@ -68,6 +68,10 @@ function poller_run(&$argv, &$argc){ while ($r = q("SELECT * FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `created` LIMIT 1")) { + // Constantly check the number of available database connections to let the frontend be accessible at any time + if (poller_max_connections_reached()) + return; + // Count active workers and compare them with a maximum value that depends on the load if (poller_too_much_workers(3)) return; @@ -126,15 +130,43 @@ function poller_run(&$argv, &$argc){ * @return bool Are more than 3/4 of the maximum connections used? */ function poller_max_connections_reached() { - $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_connections'"); - if (!$r) - return false; // Fetch the max value from the config. This is needed when the system cannot detect the correct value by itself. - // In that case we use the processlist to determine the current number of connections $max = get_config("system", "max_connections"); if ($max == 0) { + // the maximum number of possible user connections can be a system variable + $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_user_connections'"); + if ($r) + $max = $r[0]["Value"]; + + // Or it can be granted. This overrides the system variable + $r = q("SHOW GRANTS"); + if ($r) + foreach ($r AS $grants) { + $grant = array_pop($grants); + if (stristr($grant, "GRANT USAGE ON")) + if (preg_match("/WITH MAX_USER_CONNECTIONS (\d*)/", $grant, $match)) + $max = $match[1]; + } + } + + // If $max is set we will use the processlist to determine the current number of connections + // The processlist only shows entries of the current user + if ($max != 0) { + $r = q("SHOW PROCESSLIST"); + if (!$r) + return false; + + $used = count($r); + + logger("Connection usage (user values): ".$used."/".$max, LOGGER_DEBUG); + } else { + // Since there are no user specific limitations we will now check for the system values + $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_connections'"); + if (!$r) + return false; + $max = intval($r[0]["Value"]); if ($max == 0) return false; @@ -143,25 +175,19 @@ function poller_max_connections_reached() { if (!$r) return false; - $connected = intval($r[0]["Value"]); - if ($connected == 0) - return false; - } else { - $r = q("SHOW PROCESSLIST"); - if (!$r) + $used = intval($r[0]["Value"]); + if ($used == 0) return false; - $connected = count($r); + logger("Connection usage (system values): ".$used."/".$max, LOGGER_DEBUG); } - $level = $connected / $max; - - logger("Connection usage: ".$connected."/".$max, LOGGER_DEBUG); + $level = $used / $max; if ($level < (3/4)) return false; - logger("Maximum level (3/4) of connections reached: ".$connected."/".$max); + logger("Maximum level (3/4) of connections reached: ".$used."/".$max); return true; } From 4dad744c35565129ea9ba09922474c1dc6a32e04 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 11 Feb 2016 22:35:06 +0100 Subject: [PATCH 086/273] DFRN-Bugfix: Forums should work now --- include/dfrn.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 6fa47af052..1cfa42a6b3 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -2053,10 +2053,10 @@ class dfrn { if (($item["network"] != $author["network"]) AND ($author["network"] != "")) $item["network"] = $author["network"]; - if($importer["rel"] == CONTACT_IS_FOLLOWER) { - logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); - return; - } + //if($importer["rel"] == CONTACT_IS_FOLLOWER) { + // logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); + // return; + //} } if ($entrytype == DFRN_REPLY_RC) { From b5c547174826b6145259381090843cc72a4c4790 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 12 Feb 2016 07:39:08 +0100 Subject: [PATCH 087/273] Added some documentation --- include/dfrn.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/dfrn.php b/include/dfrn.php index 1cfa42a6b3..8938a9b6f7 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -2053,6 +2053,10 @@ class dfrn { if (($item["network"] != $author["network"]) AND ($author["network"] != "")) $item["network"] = $author["network"]; + // This code was taken from the old DFRN code + // When activated, forums don't work. + // And: Why should we disallow commenting by followers? + // the behaviour is now similar to the Diaspora part. //if($importer["rel"] == CONTACT_IS_FOLLOWER) { // logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); // return; From 45c4e3455e11b2b0ef20fbc53199721951247696 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Fri, 12 Feb 2016 09:25:33 +0100 Subject: [PATCH 088/273] docs: add brief autoloading help page, fix smarty3 help page name --- doc/Home.md | 2 ++ doc/autoloader.md | 17 +++++++++++++++++ ...narty3-templates.md => smarty3-templates.md} | 0 3 files changed, 19 insertions(+) create mode 100644 doc/autoloader.md rename doc/{snarty3-templates.md => smarty3-templates.md} (100%) diff --git a/doc/Home.md b/doc/Home.md index 3b6442867c..1f9b0cfab7 100644 --- a/doc/Home.md +++ b/doc/Home.md @@ -47,8 +47,10 @@ Friendica Documentation and Resources * [Theme Development](help/themes) * [Smarty 3 Templates](help/smarty3-templates) * [Database schema documantation](help/database) +* [Class Autoloading](help/autoloader) * [Code - Reference(Doxygen generated - sets cookies)](doc/html/) + **External Resources** * [Main Website](http://friendica.com) diff --git a/doc/autoloader.md b/doc/autoloader.md new file mode 100644 index 0000000000..ea1a82b357 --- /dev/null +++ b/doc/autoloader.md @@ -0,0 +1,17 @@ +Autoloader +========== + +* [Home](help) + +There is some initial support to class autoloading in Friendica core. + +The autoloader code is in `include/autoloader.php`. +It's derived from composer autoloader code. + +Namespaces and Classes are mapped to folders and files in `library/`, +and the map must be updated by hand, because we don't use composer yet. +The mapping is defined by files in `include/autoloader/` folder. + +Currently, only HTMLPurifier library is loaded using autoloader. + + diff --git a/doc/snarty3-templates.md b/doc/smarty3-templates.md similarity index 100% rename from doc/snarty3-templates.md rename to doc/smarty3-templates.md From 89d63b25238d2628cca3124ca92b1b8ea87a5667 Mon Sep 17 00:00:00 2001 From: Fabrixxm <fabrix.xm@gmail.com> Date: Fri, 12 Feb 2016 09:26:42 +0100 Subject: [PATCH 089/273] docs: small fixes to help sidebar --- mod/help.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/help.php b/mod/help.php index 5465d3e900..7222569279 100644 --- a/mod/help.php +++ b/mod/help.php @@ -62,7 +62,7 @@ function help_content(&$a) { if ($filename !== "Home") { // create TOC but not for home $lines = explode("\n", $html); - $toc="<style>aside ul {padding-left: 1em;}</style><h2>TOC</h2><ul id='toc'>"; + $toc="<style>aside ul {padding-left: 1em;}aside h1{font-size:2em}</style><h2>TOC</h2><ul id='toc'>"; $lastlevel=1; $idnum = array(0,0,0,0,0,0,0); foreach($lines as &$line){ @@ -84,7 +84,7 @@ function help_content(&$a) { } } } - for($k=1;$k<$lastlevel; $k++) $toc.="</ul>"; + for($k=0;$k<$lastlevel; $k++) $toc.="</ul>"; $html = implode("\n",$lines); $a->page['aside'] = $toc.$a->page['aside']; From acb09d3a3d34ba180ef8804abf5e032b725f62e3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 12 Feb 2016 11:04:25 +0100 Subject: [PATCH 090/273] Database connections: When we now check for user values we check the system values as well --- include/poller.php | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/include/poller.php b/include/poller.php index d68e8081ca..90e94ede9e 100644 --- a/include/poller.php +++ b/include/poller.php @@ -161,35 +161,42 @@ function poller_max_connections_reached() { $used = count($r); logger("Connection usage (user values): ".$used."/".$max, LOGGER_DEBUG); - } else { - // Since there are no user specific limitations we will now check for the system values - $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_connections'"); - if (!$r) - return false; - $max = intval($r[0]["Value"]); - if ($max == 0) - return false; + $level = $used / $max; - $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'"); - if (!$r) - return false; - - $used = intval($r[0]["Value"]); - if ($used == 0) - return false; - - logger("Connection usage (system values): ".$used."/".$max, LOGGER_DEBUG); + if ($level >= (3/4)) { + logger("Maximum level (3/4) of user connections reached: ".$used."/".$max); + return true; + } } + // We will now check for the system values. + // This limit could be reached although the user limits are fine. + $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_connections'"); + if (!$r) + return false; + + $max = intval($r[0]["Value"]); + if ($max == 0) + return false; + + $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'"); + if (!$r) + return false; + + $used = intval($r[0]["Value"]); + if ($used == 0) + return false; + + logger("Connection usage (system values): ".$used."/".$max, LOGGER_DEBUG); + $level = $used / $max; if ($level < (3/4)) return false; - logger("Maximum level (3/4) of connections reached: ".$used."/".$max); + logger("Maximum level (3/4) of system connections reached: ".$used."/".$max); return true; - } /** From a7498ef50dec2be410125da102285856d6219121 Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Sat, 13 Feb 2016 10:57:37 +0100 Subject: [PATCH 091/273] doc: add quick intro to autoloading --- doc/autoloader.md | 192 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/doc/autoloader.md b/doc/autoloader.md index ea1a82b357..947eade23c 100644 --- a/doc/autoloader.md +++ b/doc/autoloader.md @@ -15,3 +15,195 @@ The mapping is defined by files in `include/autoloader/` folder. Currently, only HTMLPurifier library is loaded using autoloader. +## A quick introdution to class autoloading + +The autoloader it's a way for php to automagically include the file that define a class when the class is first used, without the need to use "require_once" every time. + +Once is setup you don't have to use it in any way. You need a class? you use the class. + +At his basic is a function passed to the "spl_autoload_register()" function, which receive as argument the class name the script want and is it job to include the correct php file where that class is defined. +The best source for documentation is [php site](http://php.net/manual/en/language.oop5.autoload.php). + +One example, based on fictional friendica code. + +Let's say you have a php file in "include/" that define a very useful class: + +``` + file: include/ItemsManager.php + <?php + namespace \Friendica; + + class ItemsManager { + public function getAll() { ... } + public function getByID($id) { ... } + } +``` + +The class "ItemsManager" has been declared in "Friendica" namespace. +Namespaces are useful to keep things separated and avoid names clash (could be that a library you want to use defines a class named "ItemsManager", but as long as is in another namespace, you don't have any problem) + +If we were using composer, we had configured it with path where to find the classes of "Friendica" namespace, and then the composer script will generate the autoloader machinery for us. +As we don't use composer, we need check that the autoloader knows the Friendica namespace. +So in "include/autoloader/autoload_psr4.php" there should be something like + +``` + $vendorDir = dirname(dirname(dirname(__FILE__)))."/library"; + $baseDir = dirname($vendorDir); + return array( + "Friendica" => array($baseDir."/include"); + ); +``` + + +That tells the autoloader code to look for files that defines classes in "Friendica" namespace under "include/" folder. (And btw, that's why the file has the same name as the class it defines.) + +*note*: The structure of files in "include/autoloader/" has been copied from the code generated by composer, to ease the work of enable autoloader for external libraries under "library/" + +Let's say now that you need to load some items in a view, maybe in a fictional "mod/network.php". +Somewere at the start of the scripts, the autoloader was initialized. In Friendica is done at the top of "boot.php", with "require_once('include/autoloader.php');". + +The code will be something like: + +``` + file: mod/network.php + <?php + + function network_content(&$a) { + $itemsmanager = new \Friendica\ItemsManager(); + $items = $itemsmanager->getAll(); + + // pass $items to template + // return result + } +``` + +That's a quite simple example, but look: no "require()"! +You need to use a class, you use the class and you don't need to do anything more. + +Going further: now we have a bunch of "*Manager" classes that cause some code duplication, let's define a BaseManager class, where to move all code in common between all managers: + +``` + file: include/BaseManager.php + <?php + namespace \Friendica; + + class BaseManager { + public function thatFunctionEveryManagerUses() { ... } + } +``` + +and then let's change the ItemsManager class to use this code + +``` + file: include/ItemsManager.php + <?php + namespace \Friendica; + + class ItemsManager extends BaseManager { + public function getAll() { ... } + public function getByID($id) { ... } + } +``` + +The autoloader don't mind what you need the class for. You need a class, you get the class. +It works with the "BaseManager" example here, it works when we need to call static methods on a class: + +``` + file: include/dfrn.php + <?php + namespace \Friendica; + + class dfrn { + public static function mail($item, $owner) { ... } + } +``` + +``` + file: mod/mail.php + <?php + + mail_post($a){ + ... + \Friendica\dfrn::mail($item, $owner); + ... + } +``` + +If your code is in same namespace as the class you need, you don't need to prepend it: + +``` + file: include/delivery.php + <?php + + namespace \Friendica; + + // this is the same content of current include/delivery.php, + // but has been declared to be in "Friendica" namespace + + [...] + switch($contact['network']) { + + case NETWORK_DFRN: + if ($mail) { + $item['body'] = ... + $atom = dfrn::mail($item, $owner); + } elseif ($fsuggest) { + $atom = dfrn::fsuggest($item, $owner); + q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id'])); + } elseif ($relocate) + $atom = dfrn::relocate($owner, $uid); + [...] +``` + +This is real "include/delivery.php" unchanged, but as the code is declared to be in "Friendica" namespace, you don't need to write it when you need to use the "dfrn" class. +But if you want to use classes from another library, you need to use the full namespace, e.g. + +``` + <?php + namespace \Frienidca; + + class Diaspora { + public function md2bbcode() { + $html = \Michelf\MarkdownExtra::defaultTransform($text); + } + } +``` + +if you use that class in many places of the code and you don't want to write the full path to the class everytime, you can use the "use" php keyword + +``` + <?php + namespace \Frienidca; + + use \Michelf\MarkdownExtra; + + class Diaspora { + public function md2bbcode() { + $html = MarkdownExtra::defaultTransform($text); + } + } +``` + +Note that namespaces are like paths in filesystem, separated by "\", with the first "\" being the global scope. +You can go more deep if you want to, like: + +``` + <?php + namespace \Friendica\Network; + + class DFRN { + } +``` + +or + +``` + <?php + namespace \Friendica\DBA; + + class MySQL { + } +``` + +So you can think of namespaces as folders in a unix filesystem, with global scope as the root ("\"). + From 2ca6cdf6b6e0fa4a04fb1d1fb6bf5d83f9ba74e8 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 13 Feb 2016 12:26:58 +0100 Subject: [PATCH 092/273] Improvements how gcontact entries are updated --- boot.php | 26 +++-- database.sql | 18 +--- include/Scrape.php | 15 ++- include/dfrn.php | 50 ++++++---- include/items.php | 13 +-- include/socgraph.php | 219 ++++++++++++------------------------------- mod/noscrape.php | 6 +- 7 files changed, 133 insertions(+), 214 deletions(-) diff --git a/boot.php b/boot.php index 4ef30eadac..2b7d814123 100644 --- a/boot.php +++ b/boot.php @@ -1037,19 +1037,29 @@ class App { $this->performance[$value] += (float)$duration; $this->performance["marktime"] += (float)$duration; - // Trace the different functions with their timestamps - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); + $callstack = $this->callstack(); + $this->callstack[$value][$callstack] += (float)$duration; + + } + + /** + * @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); - $function = array(); + $callstack = array(); foreach ($trace AS $func) - $function[] = $func["function"]; - - $function = implode(", ", $function); - - $this->callstack[$value][$function] += (float)$duration; + $callstack[] = $func["function"]; + return implode(", ", $callstack); } function mark_timestamp($mark) { diff --git a/database.sql b/database.sql index 70b315ea24..25faf0f4c0 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 3.5-dev (Asparagus) --- DB_UPDATE_VERSION 1193 +-- DB_UPDATE_VERSION 1194 -- ------------------------------------------ @@ -119,6 +119,7 @@ CREATE TABLE IF NOT EXISTS `contact` ( `keywords` text NOT NULL, `gender` varchar(32) NOT NULL DEFAULT '', `attag` varchar(255) NOT NULL DEFAULT '', + `avatar` varchar(255) NOT NULL DEFAULT '', `photo` text NOT NULL, `thumb` text NOT NULL, `micro` text NOT NULL, @@ -411,21 +412,6 @@ CREATE TABLE IF NOT EXISTS `gserver` ( INDEX `nurl` (`nurl`) ) DEFAULT CHARSET=utf8; --- --- TABLE guid --- -CREATE TABLE IF NOT EXISTS `guid` ( - `id` int(10) unsigned NOT NULL auto_increment, - `guid` varchar(255) NOT NULL DEFAULT '', - `plink` varchar(255) NOT NULL DEFAULT '', - `uri` varchar(255) NOT NULL DEFAULT '', - `network` varchar(32) NOT NULL DEFAULT '', - PRIMARY KEY(`id`), - INDEX `guid` (`guid`), - INDEX `plink` (`plink`), - INDEX `uri` (`uri`) -) DEFAULT CHARSET=utf8; - -- -- TABLE hook -- diff --git a/include/Scrape.php b/include/Scrape.php index ca6489b16a..bc6aebbdcf 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -12,6 +12,18 @@ function scrape_dfrn($url, $dont_probe = false) { logger('scrape_dfrn: url=' . $url); + // Try to fetch the data from noscrape. This is faster than parsing the HTML + $noscrape = str_replace("/hcard/", "/noscrape/", $url); + $noscrapejson = fetch_url($noscrape); + $noscrapedata = array(); + if ($noscrapejson) { + $noscrapedata = json_decode($noscrapejson, true); + + if (is_array($noscrapedata)) + if ($noscrapedata["nick"] != "") + return($noscrapedata); + } + $s = fetch_url($url); if(! $s) @@ -91,8 +103,7 @@ function scrape_dfrn($url, $dont_probe = false) { } } } - - return $ret; + return array_merge($ret, $noscrapedata); }} diff --git a/include/dfrn.php b/include/dfrn.php index 8938a9b6f7..a37aaf29ce 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -1115,13 +1115,13 @@ class dfrn { * * @return Returns an array with relevant data of the author */ - private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch) { + private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch, $xml = "") { $author = array(); $author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue; $author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue; - $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, + $r = q("SELECT `id`, `uid`, `url`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); @@ -1130,6 +1130,9 @@ class dfrn { $author["contact-id"] = $r[0]["id"]; $author["network"] = $r[0]["network"]; } else { + if (!$onlyfetch) + logger("Contact ".$author["link"]." wasn't found for user ".$importer["uid"]." XML: ".$xml, LOGGER_DEBUG); + $author["contact-id"] = $importer["id"]; $author["network"] = $importer["network"]; $onlyfetch = true; @@ -1159,38 +1162,41 @@ class dfrn { } if ($r AND !$onlyfetch) { + logger("Check if contact details for contact ".$r[0]["id"]." (".$r[0]["nick"].") have to be updated.", LOGGER_DEBUG); + + $poco = array("url" => $contact["url"]); // When was the last change to name or uri? $name_element = $xpath->query($element."/atom:name", $context)->item(0); foreach($name_element->attributes AS $attributes) if ($attributes->name == "updated") - $contact["name-date"] = $attributes->textContent; + $poco["name-date"] = $attributes->textContent; $link_element = $xpath->query($element."/atom:link", $context)->item(0); foreach($link_element->attributes AS $attributes) if ($attributes->name == "updated") - $contact["uri-date"] = $attributes->textContent; + $poco["uri-date"] = $attributes->textContent; // Update contact data $value = $xpath->evaluate($element."/dfrn:handle/text()", $context)->item(0)->nodeValue; if ($value != "") - $contact["addr"] = $value; + $poco["addr"] = $value; $value = $xpath->evaluate($element."/poco:displayName/text()", $context)->item(0)->nodeValue; if ($value != "") - $contact["name"] = $value; + $poco["name"] = $value; $value = $xpath->evaluate($element."/poco:preferredUsername/text()", $context)->item(0)->nodeValue; if ($value != "") - $contact["nick"] = $value; + $poco["nick"] = $value; $value = $xpath->evaluate($element."/poco:note/text()", $context)->item(0)->nodeValue; if ($value != "") - $contact["about"] = $value; + $poco["about"] = $value; $value = $xpath->evaluate($element."/poco:address/poco:formatted/text()", $context)->item(0)->nodeValue; if ($value != "") - $contact["location"] = $value; + $poco["location"] = $value; /// @todo Add support for the following fields that we don't support by now in the contact table: /// - poco:utcOffset @@ -1207,7 +1213,7 @@ class dfrn { $tags[$tag->nodeValue] = $tag->nodeValue; if (count($tags)) - $contact["keywords"] = implode(", ", $tags); + $poco["keywords"] = implode(", ", $tags); // "dfrn:birthday" contains the birthday converted to UTC $old_bdyear = $contact["bdyear"]; @@ -1217,7 +1223,7 @@ class dfrn { if (strtotime($birthday) > time()) { $bd_timestamp = strtotime($birthday); - $contact["bdyear"] = date("Y", $bd_timestamp); + $poco["bdyear"] = date("Y", $bd_timestamp); } // "poco:birthday" is the birthday in the format "yyyy-mm-dd" @@ -1232,9 +1238,11 @@ class dfrn { $bdyear = $bdyear + 1; } - $contact["bd"] = $value; + $poco["bd"] = $value; } + $contact = array_merge($contact, $poco); + if ($old_bdyear != $contact["bdyear"]) self::birthday_event($contact, $birthday); @@ -1245,6 +1253,7 @@ class dfrn { unset($fields["id"]); unset($fields["uid"]); + unset($fields["url"]); unset($fields["avatar-date"]); unset($fields["name-date"]); unset($fields["uri-date"]); @@ -1264,7 +1273,7 @@ class dfrn { } if ($update) { - logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); + logger("Update contact data for contact ".$contact["id"]." (".$contact["nick"].")", LOGGER_DEBUG); q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s', @@ -1283,9 +1292,10 @@ class dfrn { // It is used in the socgraph.php to prevent that old contact data // that was relayed over several servers can overwrite contact // data that we received directly. - $contact["generation"] = 2; - $contact["photo"] = $author["avatar"]; - update_gcontact($contact); + + $poco["generation"] = 2; + $poco["photo"] = $author["avatar"]; + update_gcontact($poco); } return($author); @@ -2369,8 +2379,14 @@ class dfrn { $header["contact-id"] = $importer["id"]; // Update the contact table if the data has changed + + // The "atom:author" is only present in feeds + if ($xpath->query("/atom:feed/atom:author")->length > 0) + self::fetchauthor($xpath, $doc->firstChild, $importer, "atom:author", false, $xml); + // Only the "dfrn:owner" in the head section contains all data - self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false); + if ($xpath->query("/atom:feed/dfrn:owner")->length > 0) + self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false, $xml); logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG); diff --git a/include/items.php b/include/items.php index 1af6fe1b52..8d6b5b471c 100644 --- a/include/items.php +++ b/include/items.php @@ -500,14 +500,8 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : ''); - if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); - foreach ($trace AS $func) - $function[] = $func["function"]; - - $function = implode(", ", $function); - logger("Both author-link and owner-link are empty. Called by: ".$function, LOGGER_DEBUG); - } + if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) + logger("Both author-link and owner-link are empty. Called by: ".App::callstack(), LOGGER_DEBUG); if ($arr['plink'] == "") { $a = get_app(); @@ -888,9 +882,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa logger('item_store: new item not found in DB, id ' . $current_post); } - // Add every contact of the post to the global contact table - poco_store($arr); - create_tags_from_item($current_post); create_files_from_item($current_post); diff --git a/include/socgraph.php b/include/socgraph.php index 3b8e9140f8..8ad7a4cd31 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -10,6 +10,7 @@ require_once('include/datetime.php'); require_once("include/Scrape.php"); require_once("include/html2bbcode.php"); +require_once("include/Contact.php"); /* @@ -428,7 +429,7 @@ function poco_last_updated($profile, $force = false) { if (($gcontacts[0]["server_url"] != "") AND ($gcontacts[0]["nick"] != "")) { // Use noscrape if possible - $server = q("SELECT `noscrape` FROM `gserver` WHERE `nurl` = '%s' AND `noscrape` != ''", dbesc(normalise_link($gcontacts[0]["server_url"]))); + $server = q("SELECT `noscrape`, `network` FROM `gserver` WHERE `nurl` = '%s' AND `noscrape` != ''", dbesc(normalise_link($gcontacts[0]["server_url"]))); if ($server) { $noscraperet = z_fetch_url($server[0]["noscrape"]."/".$gcontacts[0]["nick"]); @@ -437,67 +438,42 @@ function poco_last_updated($profile, $force = false) { $noscrape = json_decode($noscraperet["body"], true); - if (($noscrape["fn"] != "") AND ($noscrape["fn"] != $gcontacts[0]["name"])) - q("UPDATE `gcontact` SET `name` = '%s' WHERE `nurl` = '%s'", - dbesc($noscrape["fn"]), dbesc(normalise_link($profile))); + $contact = array("url" => $profile, + "network" => $server[0]["network"], + "generation" => $gcontacts[0]["generation"]); - if (($noscrape["photo"] != "") AND ($noscrape["photo"] != $gcontacts[0]["photo"])) - q("UPDATE `gcontact` SET `photo` = '%s' WHERE `nurl` = '%s'", - dbesc($noscrape["photo"]), dbesc(normalise_link($profile))); + $contact["name"] = $noscrape["fn"]; + $contact["community"] = $noscrape["comm"]; - if (($noscrape["updated"] != "") AND ($noscrape["updated"] != $gcontacts[0]["updated"])) - q("UPDATE `gcontact` SET `updated` = '%s' WHERE `nurl` = '%s'", - dbesc($noscrape["updated"]), dbesc(normalise_link($profile))); - - if (($noscrape["gender"] != "") AND ($noscrape["gender"] != $gcontacts[0]["gender"])) - q("UPDATE `gcontact` SET `gender` = '%s' WHERE `nurl` = '%s'", - dbesc($noscrape["gender"]), dbesc(normalise_link($profile))); - - if (($noscrape["pdesc"] != "") AND ($noscrape["pdesc"] != $gcontacts[0]["about"])) - q("UPDATE `gcontact` SET `about` = '%s' WHERE `nurl` = '%s'", - dbesc($noscrape["pdesc"]), dbesc(normalise_link($profile))); - - if (($noscrape["about"] != "") AND ($noscrape["about"] != $gcontacts[0]["about"])) - q("UPDATE `gcontact` SET `about` = '%s' WHERE `nurl` = '%s'", - dbesc($noscrape["about"]), dbesc(normalise_link($profile))); - - if (isset($noscrape["comm"]) AND ($noscrape["comm"] != $gcontacts[0]["community"])) - q("UPDATE `gcontact` SET `community` = %d WHERE `nurl` = '%s'", - intval($noscrape["comm"]), dbesc(normalise_link($profile))); - - if (isset($noscrape["tags"])) + if (isset($noscrape["tags"])) { $keywords = implode(" ", $noscrape["tags"]); - else - $keywords = ""; - - if (($keywords != "") AND ($keywords != $gcontacts[0]["keywords"])) - q("UPDATE `gcontact` SET `keywords` = '%s' WHERE `nurl` = '%s'", - dbesc($keywords), dbesc(normalise_link($profile))); - - $location = $noscrape["locality"]; - - if ($noscrape["region"] != "") { - if ($location != "") - $location .= ", "; - - $location .= $noscrape["region"]; + if ($keywords != "") + $contact["keywords"] = $keywords; } - if ($noscrape["country-name"] != "") { - if ($location != "") - $location .= ", "; + $location = formatted_location($noscrape); + if ($location) + $contact["location"] = $location; - $location .= $noscrape["country-name"]; - } + $contact["notify"] = $noscrape["dfrn-notify"]; - if (($location != "") AND ($location != $gcontacts[0]["location"])) - q("UPDATE `gcontact` SET `location` = '%s' WHERE `nurl` = '%s'", - dbesc($location), dbesc(normalise_link($profile))); + // Remove all fields that are not present in the gcontact table + unset($noscrape["fn"]); + unset($noscrape["key"]); + unset($noscrape["homepage"]); + unset($noscrape["comm"]); + unset($noscrape["tags"]); + unset($noscrape["locality"]); + unset($noscrape["region"]); + unset($noscrape["country-name"]); + unset($noscrape["contacts"]); + unset($noscrape["dfrn-request"]); + unset($noscrape["dfrn-confirm"]); + unset($noscrape["dfrn-notify"]); + unset($noscrape["dfrn-poll"]); - // If we got data from noscrape then mark the contact as reachable - if (is_array($noscrape) AND count($noscrape)) - q("UPDATE `gcontact` SET `last_contact` = '%s' WHERE `nurl` = '%s'", - dbesc(datetime_convert()), dbesc(normalise_link($profile))); + $contact = array_merge($contact, $noscrape); + update_gcontact($contact); return $noscrape["updated"]; } @@ -534,25 +510,22 @@ function poco_last_updated($profile, $force = false) { return false; } - if (($data["name"] != "") AND ($data["name"] != $gcontacts[0]["name"])) - q("UPDATE `gcontact` SET `name` = '%s' WHERE `nurl` = '%s'", - dbesc($data["name"]), dbesc(normalise_link($profile))); + $contact = array("generation" => $gcontacts[0]["generation"]); - if (($data["nick"] != "") AND ($data["nick"] != $gcontacts[0]["nick"])) - q("UPDATE `gcontact` SET `nick` = '%s' WHERE `nurl` = '%s'", - dbesc($data["nick"]), dbesc(normalise_link($profile))); + $contact = array_merge($contact, $data); - if (($data["addr"] != "") AND ($data["addr"] != $gcontacts[0]["connect"])) - q("UPDATE `gcontact` SET `connect` = '%s' WHERE `nurl` = '%s'", - dbesc($data["addr"]), dbesc(normalise_link($profile))); + $contact["server_url"] = $data["baseurl"]; - if (($data["photo"] != "") AND ($data["photo"] != $gcontacts[0]["photo"])) - q("UPDATE `gcontact` SET `photo` = '%s' WHERE `nurl` = '%s'", - dbesc($data["photo"]), dbesc(normalise_link($profile))); + unset($contact["batch"]); + unset($contact["poll"]); + unset($contact["request"]); + unset($contact["confirm"]); + unset($contact["poco"]); + unset($contact["priority"]); + unset($contact["pubkey"]); + unset($contact["baseurl"]); - if (($data["baseurl"] != "") AND ($data["baseurl"] != $gcontacts[0]["server_url"])) - q("UPDATE `gcontact` SET `server_url` = '%s' WHERE `nurl` = '%s'", - dbesc($data["baseurl"]), dbesc(normalise_link($profile))); + update_gcontact($contact); $feedret = z_fetch_url($data["poll"]); @@ -921,88 +894,6 @@ function poco_check_server($server_url, $network = "", $force = false) { return !$failure; } -function poco_contact_from_body($body, $created, $cid, $uid) { - preg_replace_callback("/\[share(.*?)\].*?\[\/share\]/ism", - function ($match) use ($created, $cid, $uid){ - return(sub_poco_from_share($match, $created, $cid, $uid)); - }, $body); -} - -function sub_poco_from_share($share, $created, $cid, $uid) { - $profile = ""; - preg_match("/profile='(.*?)'/ism", $share[1], $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - preg_match('/profile="(.*?)"/ism', $share[1], $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - if ($profile == "") - return; - - logger("prepare poco_check for profile ".$profile, LOGGER_DEBUG); - poco_check($profile, "", "", "", "", "", "", "", "", $created, 3, $cid, $uid); -} - -function poco_store($item) { - - // Isn't it public? - if ($item['private']) - return; - - // Or is it from a network where we don't store the global contacts? - if (!in_array($item["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, NETWORK_STATUSNET, ""))) - return; - - // Is it a global copy? - $store_gcontact = ($item["uid"] == 0); - - // Is it a comment on a global copy? - if (!$store_gcontact AND ($item["uri"] != $item["parent-uri"])) { - $q = q("SELECT `id` FROM `item` WHERE `uri`='%s' AND `uid` = 0", $item["parent-uri"]); - $store_gcontact = count($q); - } - - if (!$store_gcontact) - return; - - // "3" means: We don't know this contact directly (Maybe a reshared item) - $generation = 3; - $network = ""; - $profile_url = $item["author-link"]; - - // Is it a user from our server? - $q = q("SELECT `id` FROM `contact` WHERE `self` AND `nurl` = '%s' LIMIT 1", - dbesc(normalise_link($item["author-link"]))); - if (count($q)) { - logger("Our user (generation 1): ".$item["author-link"], LOGGER_DEBUG); - $generation = 1; - $network = NETWORK_DFRN; - } else { // Is it a contact from a user on our server? - $q = q("SELECT `network`, `url` FROM `contact` WHERE `uid` != 0 AND `network` != '' - AND (`nurl` = '%s' OR `alias` IN ('%s', '%s')) AND `network` != '%s' LIMIT 1", - dbesc(normalise_link($item["author-link"])), - dbesc(normalise_link($item["author-link"])), - dbesc($item["author-link"]), - dbesc(NETWORK_STATUSNET)); - if (count($q)) { - $generation = 2; - $network = $q[0]["network"]; - $profile_url = $q[0]["url"]; - logger("Known contact (generation 2): ".$profile_url, LOGGER_DEBUG); - } - } - - if ($generation == 3) - logger("Unknown contact (generation 3): ".$item["author-link"], LOGGER_DEBUG); - - poco_check($profile_url, $item["author-name"], $network, $item["author-avatar"], "", "", "", "", "", $item["received"], $generation, $item["contact-id"], $item["uid"]); - - // Maybe its a body with a shared item? Then extract a global contact from it. - poco_contact_from_body($item["body"], $item["received"], $item["contact-id"], $item["uid"]); -} - function count_common_friends($uid,$cid) { $r = q("SELECT count(*) as `total` @@ -1533,7 +1424,7 @@ function update_gcontact($contact) { // assign all unassigned fields from the database entry foreach ($fields AS $field => $data) - if (!isset($contact[$field])) + if (!isset($contact[$field]) OR ($contact[$field] == "")) $contact[$field] = $r[0][$field]; if ($contact["network"] == NETWORK_STATUSNET) @@ -1546,14 +1437,22 @@ function update_gcontact($contact) { $update = false; unset($fields["generation"]); - foreach ($fields AS $field => $data) - if ($contact[$field] != $r[0][$field]) - $update = true; + if ((($contact["generation"] > 0) AND ($contact["generation"] <= $r[0]["generation"])) OR ($r[0]["generation"] == 0)) { + foreach ($fields AS $field => $data) + if ($contact[$field] != $r[0][$field]) { + logger("Difference for contact ".$contact["url"]." in field '".$field."'. New value: '".$contact[$field]."', old value '".$r[0][$field]."'", LOGGER_DEBUG); + $update = true; + } - if ($contact["generation"] < $r[0]["generation"]) - $update = true; + if ($contact["generation"] < $r[0]["generation"]) { + logger("Difference for contact ".$contact["url"]." in field 'generation'. new value: '".$contact["generation"]."', old value '".$r[0]["generation"]."'", LOGGER_DEBUG); + $update = true; + } + } if ($update) { + logger("Update gcontact for ".$contact["url"]." Callstack: ".App::callstack(), LOGGER_DEBUG); + q("UPDATE `gcontact` SET `photo` = '%s', `name` = '%s', `nick` = '%s', `addr` = '%s', `network` = '%s', `birthday` = '%s', `gender` = '%s', `keywords` = '%s', `hide` = %d, `nsfw` = %d, `alias` = '%s', `notify` = '%s', `url` = '%s', @@ -1581,8 +1480,10 @@ function update_gcontact($contact) { function update_gcontact_from_probe($url) { $data = probe_url($url); - if ($data["network"] != NETWORK_PHANTOM) - update_gcontact($data); + if ($data["network"] == NETWORK_PHANTOM) + return; + + update_gcontact($data); } /** diff --git a/mod/noscrape.php b/mod/noscrape.php index 51bd7234cf..1f7105b769 100644 --- a/mod/noscrape.php +++ b/mod/noscrape.php @@ -22,13 +22,17 @@ function noscrape_init(&$a) { $keywords = str_replace(array('#',',',' ',',,'),array('',' ',',',','),$keywords); $keywords = explode(',', $keywords); + $r = q("SELECT `photo` FROM `contact` WHERE `self` AND `uid` = %d", + intval($a->profile['uid'])); + $json_info = array( 'fn' => $a->profile['name'], 'addr' => $a->profile['addr'], + 'nick' => $a->user['nickname'], 'key' => $a->profile['pubkey'], 'homepage' => $a->get_baseurl()."/profile/{$which}", 'comm' => (x($a->profile,'page-flags')) && ($a->profile['page-flags'] == PAGE_COMMUNITY), - 'photo' => $a->profile['photo'], + 'photo' => $r[0]["photo"], 'tags' => $keywords ); From 253ba45c1a9f080b5086decc9696c9cda89c7ca3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 13 Feb 2016 13:30:53 +0100 Subject: [PATCH 093/273] Added a to-do --- include/dfrn.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/dfrn.php b/include/dfrn.php index a37aaf29ce..1db2c6b33d 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -1965,6 +1965,8 @@ class dfrn { $item['body'] = @html2bbcode($item['body']); } + /// @todo We should check for a repeated post and if we know the repeated author. + // We don't need the content element since "dfrn:env" is always present //$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue; From a4da9fb55db84fe8a88129fe542422b70ae8e68e Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 13 Feb 2016 14:09:08 +0100 Subject: [PATCH 094/273] Update the gcontact entry when the contact entry is checked for updates --- include/follow.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/follow.php b/include/follow.php index 22ff079b63..410e0e58aa 100644 --- a/include/follow.php +++ b/include/follow.php @@ -1,5 +1,6 @@ <?php require_once("include/Scrape.php"); +require_once("include/socgraph.php"); function update_contact($id) { /* @@ -43,6 +44,9 @@ function update_contact($id) { intval($id) ); + // Update the corresponding gcontact entry + poco_last_updated($ret["url"]); + return true; } From 307f90cdfe3df9165c8f3cd05e91c3bf18b772a3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 13 Feb 2016 14:21:53 +0100 Subject: [PATCH 095/273] Vier: Pictures in the side bar shouldn't be larger than the side bar --- view/theme/vier/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index 2b78d25d7f..e35556f541 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -1141,6 +1141,10 @@ aside h4, right_aside h4 { font-size: 1.17em; } +aside img { + max-width: 175px; +} + .nets-ul { margin-top: 0px; } From bf7dedb03b96be90947d40829bdcac68869e250b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 13 Feb 2016 18:15:24 +0100 Subject: [PATCH 096/273] Optimized query for unread group postings --- include/group.php | 23 +++++++++++------------ mod/ping.php | 18 ++++++++++-------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/include/group.php b/include/group.php index a1375e00df..2b872f16a7 100644 --- a/include/group.php +++ b/include/group.php @@ -215,7 +215,7 @@ function mini_group_select($uid,$gid = 0) { /** * @brief Create group sidebar widget - * + * * @param string $every * @param string $each * @param string $editmode @@ -234,7 +234,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro return ''; $groups = array(); - + $groups[] = array( 'text' => t('Everybody'), 'id' => 0, @@ -255,7 +255,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro if(count($r)) { foreach($r as $rr) { $selected = (($group_id == $rr['id']) ? ' group-selected' : ''); - + if ($editmode == "full") { $groupedit = array( 'href' => "group/".$rr['id'], @@ -264,7 +264,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro } else { $groupedit = null; } - + $groups[] = array( 'id' => $rr['id'], 'cid' => $cid, @@ -362,14 +362,13 @@ function groups_containing($uid,$c) { */ function groups_count_unseen() { - $r = q("SELECT `group`.`id`, `group`.`name`, COUNT(`item`.`id`) AS `count` FROM `group`, `group_member`, `item` - WHERE `group`.`uid` = %d - AND `item`.`uid` = %d - AND `item`.`unseen` AND `item`.`visible` - AND NOT `item`.`deleted` - AND `item`.`contact-id` = `group_member`.`contact-id` - AND `group_member`.`gid` = `group`.`id` - GROUP BY `group`.`id` ", + $r = q("SELECT `group`.`id`, `group`.`name`, + (SELECT COUNT(*) FROM `item` + WHERE `uid` = %d AND `unseen` AND + `contact-id` IN (SELECT `contact-id` FROM `group_member` + WHERE `group_member`.`gid` = `group`.`id` AND `group_member`.`uid` = %d)) AS `count` + FROM `group` WHERE `group`.`uid` = %d;", + intval(local_user()), intval(local_user()), intval(local_user()) ); diff --git a/mod/ping.php b/mod/ping.php index 50d179595e..2eb94576b3 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -219,19 +219,21 @@ function ping_init(&$a) { <home>$home</home>\r\n"; if ($register!=0) echo "<register>$register</register>"; - if ( count($groups_unseen) ) { + if (count($groups_unseen)) { echo '<groups>'; - foreach ($groups_unseen as $it) { - echo '<group id="' . $it['id'] . '">' . $it['count'] . "</group>"; - } + foreach ($groups_unseen as $it) + if ($it['count'] > 0) + echo '<group id="'.$it['id'].'">'.$it['count']."</group>"; + echo "</groups>"; } - if ( count($forums_unseen) ) { + if (count($forums_unseen)) { echo '<forums>'; - foreach ($forums_unseen as $it) { - echo '<forum id="' . $it['id'] . '">' . $it['count'] . "</forum>"; - } + foreach ($forums_unseen as $it) + if ($it['count'] > 0) + echo '<forum id="'.$it['id'].'">'.$it['count']."</forum>"; + echo "</forums>"; } From f496af08bb71080d11f9d76377605bbd16cb9090 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 13 Feb 2016 21:35:05 +0100 Subject: [PATCH 097/273] OStatus: The gcontact table will now be updated from the conversation as well --- include/ostatus.php | 74 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/include/ostatus.php b/include/ostatus.php index 00022f8c6c..983ed6e1b4 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -626,6 +626,77 @@ function check_conversations($mentions = false, $override = false) { set_config('system','ostatus_last_poll', time()); } +/** + * @brief Updates the gcontact table with actor data from the conversation + * + * @param object $actor The actor object that contains the contact data + */ +function ostatus_conv_fetch_actor($actor) { + + // We set the generation to "3" since the data here is not as reliable as the data we get on other occasions + $contact = array("network" => NETWORK_OSTATUS, "generation" => 3); + + if (isset($actor->url)) + $contact["url"] = $actor->url; + + if (isset($actor->displayName)) + $contact["name"] = $actor->displayName; + + if (isset($actor->portablecontacts_net->displayName)) + $contact["name"] = $actor->portablecontacts_net->displayName; + + if (isset($actor->portablecontacts_net->preferredUsername)) + $contact["nick"] = $actor->portablecontacts_net->preferredUsername; + + if (isset($actor->id)) + $contact["alias"] = $actor->id; + + if (isset($actor->summary)) + $contact["about"] = $actor->summary; + + if (isset($actor->portablecontacts_net->note)) + $contact["about"] = $actor->portablecontacts_net->note; + + if (isset($actor->portablecontacts_net->addresses->formatted)) + $contact["location"] = $actor->portablecontacts_net->addresses->formatted; + + + if (isset($actor->image->url)) + $contact["photo"] = $actor->image->url; + + if (isset($actor->image->width)) + $avatarwidth = $actor->image->width; + + if (is_array($actor->status_net->avatarLinks)) + foreach ($actor->status_net->avatarLinks AS $avatar) { + if ($avatarsize < $avatar->width) { + $contact["photo"] = $avatar->url; + $avatarsize = $avatar->width; + } + } + + $contact["server_url"] = $contact["url"]; + + $server_url = matching($contact["server_url"], $contact["alias"]); + if (strlen($server_url) > 8) + $contact["server_url"] = $server_url; + + $server_url = matching($contact["server_url"], $contact["photo"]); + if (strlen($server_url) > 8) + $contact["server_url"] = $server_url; + + if (($contact["server_url"] == $contact["url"]) OR ($contact["server_url"] == $contact["alias"])) + unset($contact["server_url"]); + else { + $hostname = str_replace("http://", "", normalise_link($contact["server_url"])); + if ($hostname AND $contact["nick"]) + $contact["addr"] = $contact["nick"]."@".$hostname; + } + + update_gcontact($contact); +} + + function ostatus_completion($conversation_url, $uid, $item = array()) { $a = get_app(); @@ -729,6 +800,9 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { foreach ($items as $single_conv) { + // Update the gcontact table + ostatus_conv_fetch_actor($single_conv->actor); + // Test - remove before flight //$tempfile = tempnam(get_temppath(), "conversation"); //file_put_contents($tempfile, json_encode($single_conv)); From 15296b8036a670fd32891d71927df81d9db6293a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 13 Feb 2016 22:20:00 +0100 Subject: [PATCH 098/273] Avoid errors when noscrape data can't be fetched. --- include/Scrape.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/Scrape.php b/include/Scrape.php index bc6aebbdcf..d54a838afd 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -19,9 +19,11 @@ function scrape_dfrn($url, $dont_probe = false) { if ($noscrapejson) { $noscrapedata = json_decode($noscrapejson, true); - if (is_array($noscrapedata)) + if (is_array($noscrapedata)) { if ($noscrapedata["nick"] != "") return($noscrapedata); + } else + $noscrapedata = array(); } $s = fetch_url($url); From 5086b8b2a77c0d20898dd56e720a73f8228672fb Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 00:14:03 +0100 Subject: [PATCH 099/273] DFRN Bugfix: The poco data wasn't sent --- include/dfrn.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 17ef541cd3..f7a05bdb63 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -95,7 +95,7 @@ class dfrn { $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' "; - $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` + $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1", dbesc($owner_nick) @@ -105,7 +105,7 @@ class dfrn { killme(); $owner = $r[0]; - $owner_id = $owner['user_uid']; + $owner_id = $owner['uid']; $owner_nick = $owner['nickname']; $sql_post_table = ""; @@ -483,7 +483,7 @@ class dfrn { "media:width" => 175, "media:height" => 175, "href" => $owner['photo']); xml_add_element($doc, $author, "link", "", $attributes); - $birthday = feed_birthday($owner['user_uid'], $owner['timezone']); + $birthday = feed_birthday($owner['uid'], $owner['timezone']); if ($birthday) xml_add_element($doc, $author, "dfrn:birthday", $birthday); @@ -498,7 +498,7 @@ class dfrn { FROM `profile` INNER JOIN `user` ON `user`.`uid` = `profile`.`uid` WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d", - intval($owner['user_uid'])); + intval($owner['uid'])); if ($r) { $profile = $r[0]; xml_add_element($doc, $author, "poco:displayName", $profile["name"]); From b9e179207678576e6ade8243a4868b523c19c919 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 07:31:57 +0100 Subject: [PATCH 100/273] The shadow contact (contact with uid=0) will be updated now as well. --- include/socgraph.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/include/socgraph.php b/include/socgraph.php index 8ad7a4cd31..ce5b08e641 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -11,7 +11,7 @@ require_once('include/datetime.php'); require_once("include/Scrape.php"); require_once("include/html2bbcode.php"); require_once("include/Contact.php"); - +require_once("include/Photo.php"); /* * poco_load @@ -1467,6 +1467,28 @@ function update_gcontact($contact) { intval($contact["generation"]), dbesc($contact["updated"]), dbesc($contact["server_url"]), dbesc($contact["connect"]), dbesc(normalise_link($contact["url"])), intval($contact["generation"])); + + + // Now update the contact entry with the user id "0" as well. + // This is used for the shadow copies of public items. + $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0 ORDER BY `id` LIMIT 1", + dbesc(normalise_link($contact["url"]))); + + if ($r) { + logger("Update shadow contact ".$r[0]["id"], LOGGER_DEBUG); + + update_contact_avatar($contact["photo"], 0, $r[0]["id"]); + + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', + `network` = '%s', `bd` = '%s', `gender` = '%s', + `keywords` = '%s', `alias` = '%s', `url` = '%s', + `location` = '%s', `about` = '%s' + WHERE `id` = %d", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["addr"]), + dbesc($contact["network"]), dbesc($contact["birthday"]), dbesc($contact["gender"]), + dbesc($contact["keywords"]), dbesc($contact["alias"]), dbesc($contact["url"]), + dbesc($contact["location"]), dbesc($contact["about"]), intval($r[0]["id"])); + } } return $gcontact_id; From 3f5f1351b98d2e495a12ba56652d0700b9239f80 Mon Sep 17 00:00:00 2001 From: fabrixxm <fabrix.xm@gmail.com> Date: Sun, 14 Feb 2016 11:24:51 +0100 Subject: [PATCH 101/273] api: throw HTTPException instead of calling api_error directly there was some places where api_error() was called instead of throwing correct subclass of HTTPException. This was causing php errors. Dogygen comment of api_error() is updated as well --- include/api.php | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/include/api.php b/include/api.php index d54e540f79..55e39e3583 100644 --- a/include/api.php +++ b/include/api.php @@ -161,10 +161,7 @@ if (!isset($_SERVER['PHP_AUTH_USER'])) { logger('API_login: ' . print_r($_SERVER,true), LOGGER_DEBUG); header('WWW-Authenticate: Basic realm="Friendica"'); - header('HTTP/1.0 401 Unauthorized'); - die((api_error($a, 'json', "This api requires login"))); - - //die('This api requires login'); + throw new UnauthorizedException("This API requires login"); } $user = $_SERVER['PHP_AUTH_USER']; @@ -216,8 +213,9 @@ if((! $record) || (! count($record))) { logger('API_login failure: ' . print_r($_SERVER,true), LOGGER_DEBUG); header('WWW-Authenticate: Basic realm="Friendica"'); - header('HTTP/1.0 401 Unauthorized'); - die('This api requires login'); + #header('HTTP/1.0 401 Unauthorized'); + #die('This api requires login'); + throw new UnauthorizedException("This API requires login"); } authenticate_success($record); $_SESSION["allow_api"] = true; @@ -331,7 +329,8 @@ * * @param Api $a * @param string $type Return type (xml, json, rss, as) - * @param string $error Error message + * @param HTTPException $error Error object + * @return strin error message formatted as $type */ function api_error(&$a, $type, $e) { $error = ($e->getMessage()!==""?$e->getMessage():$e->httpdesc); @@ -903,7 +902,8 @@ if ($posts_day > $throttle_day) { logger('Daily posting limit reached for user '.api_user(), LOGGER_DEBUG); - die(api_error($a, $type, sprintf(t("Daily posting limit of %d posts reached. The post was rejected."), $throttle_day))); + #die(api_error($a, $type, sprintf(t("Daily posting limit of %d posts reached. The post was rejected."), $throttle_day))); + throw new TooManyRequestsException(sprintf(t("Daily posting limit of %d posts reached. The post was rejected."), $throttle_day)); } } @@ -922,7 +922,9 @@ if ($posts_week > $throttle_week) { logger('Weekly posting limit reached for user '.api_user(), LOGGER_DEBUG); - die(api_error($a, $type, sprintf(t("Weekly posting limit of %d posts reached. The post was rejected."), $throttle_week))); + #die(api_error($a, $type, sprintf(t("Weekly posting limit of %d posts reached. The post was rejected."), $throttle_week))); + throw new TooManyRequestsException(sprintf(t("Weekly posting limit of %d posts reached. The post was rejected."), $throttle_week)); + } } @@ -941,7 +943,8 @@ if ($posts_month > $throttle_month) { logger('Monthly posting limit reached for user '.api_user(), LOGGER_DEBUG); - die(api_error($a, $type, sprintf(t("Monthly posting limit of %d posts reached. The post was rejected."), $throttle_month))); + #die(api_error($a, $type, sprintf(t("Monthly posting limit of %d posts reached. The post was rejected."), $throttle_month))); + throw new TooManyRequestsException(sprintf(t("Monthly posting limit of %d posts reached. The post was rejected."), $throttle_month)); } } @@ -1809,7 +1812,7 @@ $action_argv_id=2; if ($a->argv[1]=="1.1") $action_argv_id=3; - if ($a->argc<=$action_argv_id) die(api_error($a, $type, t("Invalid request."))); + if ($a->argc<=$action_argv_id) throw new BadRequestException("Invalid request."); $action = str_replace(".".$type,"",$a->argv[$action_argv_id]); if ($a->argc==$action_argv_id+2) { $itemid = intval($a->argv[$action_argv_id+1]); From 5d35974c19b50c98695c57cc1ad9195a36e7c396 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 11:56:23 +0100 Subject: [PATCH 102/273] "addr" and "server_url" are now generated directly in "update_gcontact" if not given. --- include/Scrape.php | 69 ++++++++++++++++++++++++++++++++++---------- include/ostatus.php | 20 ------------- include/socgraph.php | 22 ++++++++++++++ 3 files changed, 75 insertions(+), 36 deletions(-) diff --git a/include/Scrape.php b/include/Scrape.php index d54a838afd..ef1db0312d 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -841,18 +841,18 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { $vcard['fn'] = $url; if (($notify != "") AND ($poll != "")) { - $baseurl = matching(normalise_link($notify), normalise_link($poll)); + $baseurl = matching_url(normalise_link($notify), normalise_link($poll)); - $baseurl2 = matching($baseurl, normalise_link($profile)); + $baseurl2 = matching_url($baseurl, normalise_link($profile)); if ($baseurl2 != "") $baseurl = $baseurl2; } if (($baseurl == "") AND ($notify != "")) - $baseurl = matching(normalise_link($profile), normalise_link($notify)); + $baseurl = matching_url(normalise_link($profile), normalise_link($notify)); if (($baseurl == "") AND ($poll != "")) - $baseurl = matching(normalise_link($profile), normalise_link($poll)); + $baseurl = matching_url(normalise_link($profile), normalise_link($poll)); $baseurl = rtrim($baseurl, "/"); @@ -907,19 +907,56 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { return $result; } -function matching($part1, $part2) { - $len = min(strlen($part1), strlen($part2)); +/** + * @brief Find the matching part between two url + * + * @param string $url1 + * @param string $url2 + * @return string The matching part + */ +function matching_url($url1, $url2) { + + if (($url1 == "") OR ($url2 == "")) + return ""; + + $url1 = normalise_link($url1); + $url2 = normalise_link($url2); + + $parts1 = parse_url($url1); + $parts2 = parse_url($url2); + + if (!isset($parts1["host"]) OR !isset($parts2["host"])) + return ""; + + if ($parts1["scheme"] != $parts2["scheme"]) + return ""; + + if ($parts1["host"] != $parts2["host"]) + return ""; + + if ($parts1["port"] != $parts2["port"]) + return ""; + + $match = $parts1["scheme"]."://".$parts1["host"]; + + if ($parts1["port"]) + $match .= ":".$parts1["port"]; + + $pathparts1 = explode("/", $parts1["path"]); + $pathparts2 = explode("/", $parts2["path"]); - $match = ""; - $matching = true; $i = 0; - while (($i <= $len) AND $matching) { - if (substr($part1, $i, 1) == substr($part2, $i, 1)) - $match .= substr($part1, $i, 1); - else - $matching = false; + $path = ""; + do { + $path1 = $pathparts1[$i]; + $path2 = $pathparts2[$i]; - $i++; - } - return($match); + if ($path1 == $path2) + $path .= $path1."/"; + + } while (($path1 == $path2) AND ($i++ <= count($pathparts1))); + + $match .= $path; + + return normalise_link($match); } diff --git a/include/ostatus.php b/include/ostatus.php index 983ed6e1b4..5c5016d0fc 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -164,8 +164,6 @@ function ostatus_fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); } - - /// @todo Add the "addr" field $contact["generation"] = 2; $contact["photo"] = $author["author-avatar"]; update_gcontact($contact); @@ -675,24 +673,6 @@ function ostatus_conv_fetch_actor($actor) { } } - $contact["server_url"] = $contact["url"]; - - $server_url = matching($contact["server_url"], $contact["alias"]); - if (strlen($server_url) > 8) - $contact["server_url"] = $server_url; - - $server_url = matching($contact["server_url"], $contact["photo"]); - if (strlen($server_url) > 8) - $contact["server_url"] = $server_url; - - if (($contact["server_url"] == $contact["url"]) OR ($contact["server_url"] == $contact["alias"])) - unset($contact["server_url"]); - else { - $hostname = str_replace("http://", "", normalise_link($contact["server_url"])); - if ($hostname AND $contact["nick"]) - $contact["addr"] = $contact["nick"]."@".$hostname; - } - update_gcontact($contact); } diff --git a/include/socgraph.php b/include/socgraph.php index ce5b08e641..e07e0d3533 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -1433,6 +1433,28 @@ function update_gcontact($contact) { if (!isset($contact["updated"])) $contact["updated"] = datetime_convert(); + if ($contact["server_url"] == "") { + $server_url = $contact["url"]; + + $server_url = matching_url($server_url, $contact["alias"]); + if ($server_url != "") + $contact["server_url"] = $server_url; + + $server_url = matching_url($server_url, $contact["photo"]); + if ($server_url != "") + $contact["server_url"] = $server_url; + + $server_url = matching_url($server_url, $contact["notify"]); + if ($server_url != "") + $contact["server_url"] = $server_url; + } else + $contact["server_url"] = normalise_link($contact["server_url"]); + + if (($contact["addr"] == "") AND ($contact["server_url"] != "") AND ($contact["nick"] != "")) { + $hostname = str_replace("http://", "", $contact["server_url"]); + $contact["addr"] = $contact["nick"]."@".$hostname; + } + // Check if any field changed $update = false; unset($fields["generation"]); From 922186bdd1a09ddc45b346e46aef4762f9b64283 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 12:39:57 +0100 Subject: [PATCH 103/273] Small bugfix for the keyword bug in the gcontact table --- include/socgraph.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/socgraph.php b/include/socgraph.php index e07e0d3533..bd5b1817f0 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -1422,6 +1422,14 @@ function update_gcontact($contact) { unset($fields["url"]); unset($fields["updated"]); + // Bugfix: We had an error in the storing of keywords which lead to the "0" + // This value is still transmitted via poco. + if ($contact["keywords"] == "0") + unset($contact["keywords"]); + + if ($r[0]["keywords"] == "0") + $r[0]["keywords"] = ""; + // assign all unassigned fields from the database entry foreach ($fields AS $field => $data) if (!isset($contact[$field]) OR ($contact[$field] == "")) From ec57b61f012562e3a2464869d62ade97fdc2e8d9 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 15:02:59 +0100 Subject: [PATCH 104/273] The feed function has now a simulation mode --- include/feed.php | 89 ++++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/include/feed.php b/include/feed.php index eb91f7efd4..6d645ecffb 100644 --- a/include/feed.php +++ b/include/feed.php @@ -2,7 +2,7 @@ require_once("include/html2bbcode.php"); require_once("include/items.php"); -function feed_import($xml,$importer,&$contact, &$hub) { +function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { $a = get_app(); @@ -25,7 +25,7 @@ function feed_import($xml,$importer,&$contact, &$hub) { // Is it RDF? if ($xpath->query('/rdf:RDF/rss:channel')->length > 0) { - //$author["author-link"] = $xpath->evaluate('/rdf:RDF/rss:channel/rss:link/text()')->item(0)->nodeValue; + $author["author-link"] = $xpath->evaluate('/rdf:RDF/rss:channel/rss:link/text()')->item(0)->nodeValue; $author["author-name"] = $xpath->evaluate('/rdf:RDF/rss:channel/rss:title/text()')->item(0)->nodeValue; if ($author["author-name"] == "") @@ -36,19 +36,26 @@ function feed_import($xml,$importer,&$contact, &$hub) { // Is it Atom? if ($xpath->query('/atom:feed/atom:entry')->length > 0) { - //$self = $xpath->query("/atom:feed/atom:link[@rel='self']")->item(0)->attributes; - //if (is_object($self)) - // foreach($self AS $attributes) - // if ($attributes->name == "href") - // $author["author-link"] = $attributes->textContent; + $self = $xpath->query("atom:link[@rel='self']")->item(0)->attributes; + if (is_object($self)) + foreach($self AS $attributes) + if ($attributes->name == "href") + $author["author-link"] = $attributes->textContent; - //if ($author["author-link"] == "") { - // $alternate = $xpath->query("/atom:feed/atom:link[@rel='alternate']")->item(0)->attributes; - // if (is_object($alternate)) - // foreach($alternate AS $attributes) - // if ($attributes->name == "href") - // $author["author-link"] = $attributes->textContent; - //} + if ($author["author-link"] == "") { + $alternate = $xpath->query("atom:link[@rel='alternate']")->item(0)->attributes; + if (is_object($alternate)) + foreach($alternate AS $attributes) + if ($attributes->name == "href") + $author["author-link"] = $attributes->textContent; + } + + if ($author["author-link"] == "") + $author["author-link"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue; + if ($author["author-link"] == "") + $author["author-link"] = $xpath->evaluate('/atom:feed/atom:id/text()')->item(0)->nodeValue; + + $author["author-avatar"] = $xpath->evaluate('/atom:feed/atom:logo/text()')->item(0)->nodeValue; $author["author-name"] = $xpath->evaluate('/atom:feed/atom:title/text()')->item(0)->nodeValue; @@ -58,8 +65,6 @@ function feed_import($xml,$importer,&$contact, &$hub) { if ($author["author-name"] == "") $author["author-name"] = $xpath->evaluate('/atom:feed/atom:author/atom:name/text()')->item(0)->nodeValue; - //$author["author-avatar"] = $xpath->evaluate('/atom:feed/atom:logo/text()')->item(0)->nodeValue; - $author["edited"] = $author["created"] = $xpath->query('/atom:feed/atom:updated/text()')->item(0)->nodeValue; $author["app"] = $xpath->evaluate('/atom:feed/atom:generator/text()')->item(0)->nodeValue; @@ -69,9 +74,10 @@ function feed_import($xml,$importer,&$contact, &$hub) { // Is it RSS? if ($xpath->query('/rss/channel')->length > 0) { - //$author["author-link"] = $xpath->evaluate('/rss/channel/link/text()')->item(0)->nodeValue; + $author["author-link"] = $xpath->evaluate('/rss/channel/link/text()')->item(0)->nodeValue; + $author["author-name"] = $xpath->evaluate('/rss/channel/title/text()')->item(0)->nodeValue; - //$author["author-avatar"] = $xpath->evaluate('/rss/channel/image/url/text()')->item(0)->nodeValue; + $author["author-avatar"] = $xpath->evaluate('/rss/channel/image/url/text()')->item(0)->nodeValue; if ($author["author-name"] == "") $author["author-name"] = $xpath->evaluate('/rss/channel/copyright/text()')->item(0)->nodeValue; @@ -86,18 +92,18 @@ function feed_import($xml,$importer,&$contact, &$hub) { $entries = $xpath->query('/rss/channel/item'); } - //if ($author["author-link"] == "") + if (is_array($contact)) { $author["author-link"] = $contact["url"]; - if ($author["author-name"] == "") - $author["author-name"] = $contact["name"]; + if ($author["author-name"] == "") + $author["author-name"] = $contact["name"]; - //if ($author["author-avatar"] == "") $author["author-avatar"] = $contact["thumb"]; - $author["owner-link"] = $contact["url"]; - $author["owner-name"] = $contact["name"]; - $author["owner-avatar"] = $contact["thumb"]; + $author["owner-link"] = $contact["url"]; + $author["owner-name"] = $contact["name"]; + $author["owner-avatar"] = $contact["thumb"]; + } $header = array(); $header["uid"] = $importer["uid"]; @@ -120,6 +126,8 @@ function feed_import($xml,$importer,&$contact, &$hub) { if (!is_object($entries)) return; + $items = array(); + $entrylist = array(); foreach ($entries AS $entry) @@ -201,13 +209,13 @@ function feed_import($xml,$importer,&$contact, &$hub) { if ($creator != "") $item["author-name"] = $creator; - //$item["object"] = $xml; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s', '%s')", - intval($importer["uid"]), dbesc($item["uri"]), dbesc(NETWORK_FEED), dbesc(NETWORK_DFRN)); - if ($r) { - logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); - continue; + if (!$simulate) { + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s', '%s')", + intval($importer["uid"]), dbesc($item["uri"]), dbesc(NETWORK_FEED), dbesc(NETWORK_DFRN)); + if ($r) { + logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); + continue; + } } /// @TODO ? @@ -272,14 +280,21 @@ function feed_import($xml,$importer,&$contact, &$hub) { $item["body"] = html2bbcode($body); } - logger("Stored feed: ".print_r($item, true), LOGGER_DEBUG); + if (!$simulate) { + logger("Stored feed: ".print_r($item, true), LOGGER_DEBUG); - $notify = item_is_remote_self($contact, $item); - $id = item_store($item, false, $notify); + $notify = item_is_remote_self($contact, $item); + $id = item_store($item, false, $notify); - //print_r($item); + logger("Feed for contact ".$contact["url"]." stored under id ".$id); + } else + $items[] = $item; - logger("Feed for contact ".$contact["url"]." stored under id ".$id); + if ($simulate) + break; } + + if ($simulate) + return array("header" => $author, "items" => $items); } ?> From 21fc3c60d478db88b251599ee984e363e0c39bf8 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 15:05:43 +0100 Subject: [PATCH 105/273] We now show the correct platform (redmatrix or hubzilla) --- include/contact_selectors.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/contact_selectors.php b/include/contact_selectors.php index f104866232..a884a6b52b 100644 --- a/include/contact_selectors.php +++ b/include/contact_selectors.php @@ -99,8 +99,16 @@ function network_to_name($s, $profile = "") { $networkname = str_replace($search,$replace,$s); - if (($s == NETWORK_DIASPORA) AND ($profile != "") AND diaspora_is_redmatrix($profile)) - $networkname = t("Redmatrix"); + if (($s == NETWORK_DIASPORA) AND ($profile != "") AND diaspora_is_redmatrix($profile)) { + $networkname = t("Hubzilla/Redmatrix"); + + $r = q("SELECT `gserver`.`platform` FROM `gcontact` + INNER JOIN `gserver` ON `gserver`.`nurl` = `gcontact`.`server_url` + WHERE `gcontact`.`nurl` = '%s' AND `platform` != ''", + dbesc(normalise_link($profile))); + if ($r) + $networkname = $r[0]["platform"]; + } return $networkname; } From 7f1f549c6f435bfe29bf7d331748b2623f98e6ae Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 15:08:49 +0100 Subject: [PATCH 106/273] Fallback when there is no nick name --- mod/item.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mod/item.php b/mod/item.php index 7e575a17e4..2ade524a05 100644 --- a/mod/item.php +++ b/mod/item.php @@ -160,6 +160,9 @@ function item_post(&$a) { logger('no contact found: '.print_r($thrparent, true), LOGGER_DEBUG); } else logger('parent contact: '.print_r($parent_contact, true), LOGGER_DEBUG); + + if ($parent_contact["nick"] == "") + $parent_contact["nick"] = $parent_contact["name"]; } } From b0548018d82cf6ecc717b3382ca5e699631458e9 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 14 Feb 2016 19:50:59 +0100 Subject: [PATCH 107/273] Simplepie is removed since we don't use it anymore --- boot.php | 9 - include/Scrape.php | 127 +- include/feed.php | 28 +- library/simplepie/LICENSE.txt | 26 - library/simplepie/README.markdown | 53 - .../COMPATIBILITY README.txt | 7 - .../sp_compatibility_test.php | 330 - library/simplepie/create.php | 178 - library/simplepie/db.sql | 38 - library/simplepie/demo/cli_test.php | 23 - .../demo/for_the_demo/alternate_favicon.png | Bin 28621 -> 0 bytes .../for_the_demo/background_blockquote.png | Bin 27353 -> 0 bytes .../demo/for_the_demo/background_menuitem.gif | Bin 533 -> 0 bytes .../for_the_demo/background_menuitem_off.gif | Bin 533 -> 0 bytes .../background_menuitem_shadow.gif | Bin 250 -> 0 bytes .../demo/for_the_demo/favicons/alternate.png | Bin 28621 -> 0 bytes .../demo/for_the_demo/favicons/blinklist.png | Bin 4377 -> 0 bytes .../demo/for_the_demo/favicons/blogmarks.png | Bin 3823 -> 0 bytes .../demo/for_the_demo/favicons/delicious.png | Bin 3739 -> 0 bytes .../demo/for_the_demo/favicons/digg.png | Bin 4004 -> 0 bytes .../demo/for_the_demo/favicons/magnolia.png | Bin 4574 -> 0 bytes .../demo/for_the_demo/favicons/myweb2.png | Bin 4010 -> 0 bytes .../demo/for_the_demo/favicons/newsvine.png | Bin 3804 -> 0 bytes .../demo/for_the_demo/favicons/reddit.png | Bin 4239 -> 0 bytes .../demo/for_the_demo/favicons/segnalo.png | Bin 4116 -> 0 bytes .../demo/for_the_demo/favicons/simpy.png | Bin 4256 -> 0 bytes .../demo/for_the_demo/favicons/spurl.png | Bin 3970 -> 0 bytes .../demo/for_the_demo/favicons/technorati.png | Bin 4087 -> 0 bytes .../demo/for_the_demo/favicons/wists.png | Bin 3974 -> 0 bytes library/simplepie/demo/for_the_demo/feed.png | Bin 715 -> 0 bytes .../demo/for_the_demo/logo_simplepie_demo.png | Bin 3047 -> 0 bytes .../demo/for_the_demo/lucida-grande-bold.swf | Bin 21159 -> 0 bytes .../demo/for_the_demo/mediaplayer.swf | Bin 32008 -> 0 bytes .../demo/for_the_demo/mediaplayer_readme.htm | 5 - .../demo/for_the_demo/mini_podcast.png | Bin 1202 -> 0 bytes .../demo/for_the_demo/place_audio.png | Bin 851 -> 0 bytes .../demo/for_the_demo/place_video.png | Bin 36713 -> 0 bytes .../demo/for_the_demo/sIFR-print.css | 35 - .../demo/for_the_demo/sIFR-screen.css | 39 - .../demo/for_the_demo/sifr-config.js | 40 - library/simplepie/demo/for_the_demo/sifr.js | 19 - .../simplepie/demo/for_the_demo/simplepie.css | 397 - .../simplepie/demo/for_the_demo/sleight.js | 31 - .../place_audio_fireworksfile.png | Bin 39177 -> 0 bytes .../place_video_fireworksfile.png | Bin 115826 -> 0 bytes .../source_files/sIFR-r245/SifrStyleSheet.as | 71 - .../source_files/sIFR-r245/_README_.txt | 12 - .../source_files/sIFR-r245/options.as | 12 - .../source_files/sIFR-r245/sIFR.as | 359 - .../source_files/sIFR-r245/sifr.fla | Bin 47104 -> 0 bytes .../demo/for_the_demo/top_gradient.gif | Bin 1378 -> 0 bytes .../simplepie/demo/for_the_demo/verdana.swf | Bin 28575 -> 0 bytes .../for_the_demo/yanone-kaffeesatz-bold.swf | Bin 76780 -> 0 bytes library/simplepie/demo/handler_image.php | 6 - library/simplepie/demo/index.php | 295 - library/simplepie/demo/minimalistic.php | 137 - library/simplepie/demo/multifeeds.php | 108 - library/simplepie/demo/test.php | 62 - library/simplepie/idn/LICENCE | 502 - library/simplepie/idn/ReadMe.txt | 123 - library/simplepie/idn/idna_convert.class.php | 969 - library/simplepie/idn/npdata.ser | 1 - library/simplepie/simplepie.inc | 15150 ---------------- util/README | 2 +- 64 files changed, 41 insertions(+), 19153 deletions(-) delete mode 100644 library/simplepie/LICENSE.txt delete mode 100644 library/simplepie/README.markdown delete mode 100644 library/simplepie/compatibility_test/COMPATIBILITY README.txt delete mode 100644 library/simplepie/compatibility_test/sp_compatibility_test.php delete mode 100644 library/simplepie/create.php delete mode 100644 library/simplepie/db.sql delete mode 100644 library/simplepie/demo/cli_test.php delete mode 100644 library/simplepie/demo/for_the_demo/alternate_favicon.png delete mode 100644 library/simplepie/demo/for_the_demo/background_blockquote.png delete mode 100644 library/simplepie/demo/for_the_demo/background_menuitem.gif delete mode 100644 library/simplepie/demo/for_the_demo/background_menuitem_off.gif delete mode 100644 library/simplepie/demo/for_the_demo/background_menuitem_shadow.gif delete mode 100644 library/simplepie/demo/for_the_demo/favicons/alternate.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/blinklist.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/blogmarks.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/delicious.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/digg.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/magnolia.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/myweb2.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/newsvine.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/reddit.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/segnalo.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/simpy.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/spurl.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/technorati.png delete mode 100644 library/simplepie/demo/for_the_demo/favicons/wists.png delete mode 100644 library/simplepie/demo/for_the_demo/feed.png delete mode 100644 library/simplepie/demo/for_the_demo/logo_simplepie_demo.png delete mode 100644 library/simplepie/demo/for_the_demo/lucida-grande-bold.swf delete mode 100644 library/simplepie/demo/for_the_demo/mediaplayer.swf delete mode 100644 library/simplepie/demo/for_the_demo/mediaplayer_readme.htm delete mode 100644 library/simplepie/demo/for_the_demo/mini_podcast.png delete mode 100644 library/simplepie/demo/for_the_demo/place_audio.png delete mode 100644 library/simplepie/demo/for_the_demo/place_video.png delete mode 100644 library/simplepie/demo/for_the_demo/sIFR-print.css delete mode 100644 library/simplepie/demo/for_the_demo/sIFR-screen.css delete mode 100644 library/simplepie/demo/for_the_demo/sifr-config.js delete mode 100644 library/simplepie/demo/for_the_demo/sifr.js delete mode 100644 library/simplepie/demo/for_the_demo/simplepie.css delete mode 100644 library/simplepie/demo/for_the_demo/sleight.js delete mode 100644 library/simplepie/demo/for_the_demo/source_files/place_audio_fireworksfile.png delete mode 100644 library/simplepie/demo/for_the_demo/source_files/place_video_fireworksfile.png delete mode 100644 library/simplepie/demo/for_the_demo/source_files/sIFR-r245/SifrStyleSheet.as delete mode 100644 library/simplepie/demo/for_the_demo/source_files/sIFR-r245/_README_.txt delete mode 100644 library/simplepie/demo/for_the_demo/source_files/sIFR-r245/options.as delete mode 100644 library/simplepie/demo/for_the_demo/source_files/sIFR-r245/sIFR.as delete mode 100644 library/simplepie/demo/for_the_demo/source_files/sIFR-r245/sifr.fla delete mode 100644 library/simplepie/demo/for_the_demo/top_gradient.gif delete mode 100644 library/simplepie/demo/for_the_demo/verdana.swf delete mode 100644 library/simplepie/demo/for_the_demo/yanone-kaffeesatz-bold.swf delete mode 100644 library/simplepie/demo/handler_image.php delete mode 100644 library/simplepie/demo/index.php delete mode 100644 library/simplepie/demo/minimalistic.php delete mode 100644 library/simplepie/demo/multifeeds.php delete mode 100644 library/simplepie/demo/test.php delete mode 100644 library/simplepie/idn/LICENCE delete mode 100644 library/simplepie/idn/ReadMe.txt delete mode 100644 library/simplepie/idn/idna_convert.class.php delete mode 100644 library/simplepie/idn/npdata.ser delete mode 100644 library/simplepie/simplepie.inc diff --git a/boot.php b/boot.php index 4ef30eadac..62b90aa2cd 100644 --- a/boot.php +++ b/boot.php @@ -588,15 +588,6 @@ class App { if(x($_SERVER,'SERVER_NAME')) { $this->hostname = $_SERVER['SERVER_NAME']; - // See bug 437 - this didn't work so disabling it - //if(stristr($this->hostname,'xn--')) { - // PHP or webserver may have converted idn to punycode, so - // convert punycode back to utf-8 - // require_once('library/simplepie/idn/idna_convert.class.php'); - // $x = new idna_convert(); - // $this->hostname = $x->decode($_SERVER['SERVER_NAME']); - //} - if(x($_SERVER,'SERVER_PORT') && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) $this->hostname .= ':' . $_SERVER['SERVER_PORT']; /* diff --git a/include/Scrape.php b/include/Scrape.php index ca6489b16a..95a3c0221f 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -2,6 +2,7 @@ require_once('library/HTML5/Parser.php'); require_once('include/crypto.php'); +require_once('include/feed.php'); if(! function_exists('scrape_dfrn')) { function scrape_dfrn($url, $dont_probe = false) { @@ -366,8 +367,6 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { $network = NETWORK_TWITTER; } - // Twitter is deactivated since twitter closed its old API - //$twitter = ((strpos($url,'twitter.com') !== false) ? true : false); $lastfm = ((strpos($url,'last.fm/user') !== false) ? true : false); $at_addr = ((strpos($url,'@') !== false) ? true : false); @@ -604,21 +603,6 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { $vcard['nick'] = $addr_parts[0]; } - /* if($twitter) { - logger('twitter: setup'); - $tid = basename($url); - $tapi = 'https://api.twitter.com/1/statuses/user_timeline.rss'; - if(intval($tid)) - $poll = $tapi . '?user_id=' . $tid; - else - $poll = $tapi . '?screen_name=' . $tid; - $profile = 'http://twitter.com/#!/' . $tid; - //$vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid; - $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image?screen_name=' . $tid . '&size=bigger'; - $vcard['nick'] = $tid; - $vcard['fn'] = $tid; - } */ - if($lastfm) { $profile = $url; $poll = str_replace(array('www.','last.fm/'),array('','ws.audioscrobbler.com/1.0/'),$url) . '/recenttracks.rss'; @@ -662,85 +646,34 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { if(x($feedret,'photo') && (! x($vcard,'photo'))) $vcard['photo'] = $feedret['photo']; - require_once('library/simplepie/simplepie.inc'); - $feed = new SimplePie(); + $cookiejar = tempnam(get_temppath(), 'cookiejar-scrape-feed-'); $xml = fetch_url($poll, false, $redirects, 0, Null, $cookiejar); unlink($cookiejar); logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA); - $a = get_app(); - logger('probe_url: scrape_feed: headers: ' . $a->get_curl_headers(), LOGGER_DATA); - - // Don't try and parse an empty string - $feed->set_raw_data(($xml) ? $xml : '<?xml version="1.0" encoding="utf-8" ?><xml></xml>'); - - $feed->init(); - if($feed->error()) { - logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error()); + if ($xml == "") { + logger("scrape_feed: XML is empty for feed ".$poll); $network = NETWORK_PHANTOM; - } + } else { + $data = feed_import($xml,$dummy1,$dummy2, $dummy3, true); - if(! x($vcard,'photo')) - $vcard['photo'] = $feed->get_image_url(); - $author = $feed->get_author(); + if (!is_array($data)) { + logger("scrape_feed: This doesn't seem to be a feed: ".$poll); + $network = NETWORK_PHANTOM; + } else { + if (($vcard["photo"] == "") AND ($data["header"]["author-avatar"] != "")) + $vcard["photo"] = $data["header"]["author-avatar"]; - if($author) { - $vcard['fn'] = unxmlify(trim($author->get_name())); - if(! $vcard['fn']) - $vcard['fn'] = trim(unxmlify($author->get_email())); - if(strpos($vcard['fn'],'@') !== false) - $vcard['fn'] = substr($vcard['fn'],0,strpos($vcard['fn'],'@')); + if (($vcard["fn"] == "") AND ($data["header"]["author-name"] != "")) + $vcard["fn"] = $data["header"]["author-name"]; - $email = unxmlify($author->get_email()); - if(! $profile && $author->get_link()) - $profile = trim(unxmlify($author->get_link())); - if(! $vcard['photo']) { - $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo')) - $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; - } - } - // Fetch fullname via poco:displayName - $pocotags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if ($pocotags) { - $elems = $pocotags[0]['child']['http://portablecontacts.net/spec/1.0']; - if (isset($elems["displayName"])) - $vcard['fn'] = $elems["displayName"][0]["data"]; - if (isset($elems["preferredUsername"])) - $vcard['nick'] = $elems["preferredUsername"][0]["data"]; - } - } - else { - $item = $feed->get_item(0); - if($item) { - $author = $item->get_author(); - if($author) { - $vcard['fn'] = trim(unxmlify($author->get_name())); - if(! $vcard['fn']) - $vcard['fn'] = trim(unxmlify($author->get_email())); - if(strpos($vcard['fn'],'@') !== false) - $vcard['fn'] = substr($vcard['fn'],0,strpos($vcard['fn'],'@')); - $email = unxmlify($author->get_email()); - if(! $profile && $author->get_link()) - $profile = trim(unxmlify($author->get_link())); - } - if(! $vcard['photo']) { - $rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/','thumbnail'); - if($rawmedia && $rawmedia[0]['attribs']['']['url']) - $vcard['photo'] = unxmlify($rawmedia[0]['attribs']['']['url']); - } - if(! $vcard['photo']) { - $rawtags = $item->get_item_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo')) - $vcard['photo'] = $elems['link'][0]['attribs']['']['href']; - } - } + if (($vcard["nick"] == "") AND ($data["header"]["author-nick"] != "")) + $vcard["nick"] = $data["header"]["author-nick"]; + + if(!$profile AND ($data["header"]["author-link"] != "") AND !in_array($network, array("", NETWORK_FEED))) + $profile = $data["header"]["author-link"]; } } @@ -783,27 +716,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { } } - if((! $vcard['photo']) && strlen($email)) - $vcard['photo'] = avatar_img($email); - if($poll === $profile) - $lnk = $feed->get_permalink(); - if(isset($lnk) && strlen($lnk)) - $profile = $lnk; - - if(! $network) { + if(! $network) $network = NETWORK_FEED; - // If it is a feed, don't take the author name as feed name - unset($vcard['fn']); - } - if(! (x($vcard,'fn'))) - $vcard['fn'] = notags($feed->get_title()); - if(! (x($vcard,'fn'))) - $vcard['fn'] = notags($feed->get_description()); - if(strpos($vcard['fn'],'Twitter / ') !== false) { - $vcard['fn'] = substr($vcard['fn'],strpos($vcard['fn'],'/')+1); - $vcard['fn'] = trim($vcard['fn']); - } if(! x($vcard,'nick')) { $vcard['nick'] = strtolower(notags(unxmlify($vcard['fn']))); if(strpos($vcard['nick'],' ')) @@ -816,7 +731,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { if(! x($vcard,'photo')) { $a = get_app(); - $vcard['photo'] = $a->get_baseurl() . '/images/person-175.jpg' ; + $vcard['photo'] = App::get_baseurl() . '/images/person-175.jpg' ; } if(! $profile) diff --git a/include/feed.php b/include/feed.php index 6d645ecffb..184b784c72 100644 --- a/include/feed.php +++ b/include/feed.php @@ -14,12 +14,13 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { $doc = new DOMDocument(); @$doc->loadXML($xml); $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom"); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); $xpath->registerNamespace('dc', "http://purl.org/dc/elements/1.1/"); $xpath->registerNamespace('content', "http://purl.org/rss/1.0/modules/content/"); $xpath->registerNamespace('rdf', "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); $xpath->registerNamespace('rss', "http://purl.org/rss/1.0/"); $xpath->registerNamespace('media', "http://search.yahoo.com/mrss/"); + $xpath->registerNamespace('poco', NAMESPACE_POCO); $author = array(); @@ -36,22 +37,23 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { // Is it Atom? if ($xpath->query('/atom:feed/atom:entry')->length > 0) { - $self = $xpath->query("atom:link[@rel='self']")->item(0)->attributes; - if (is_object($self)) - foreach($self AS $attributes) + $alternate = $xpath->query("atom:link[@rel='alternate']")->item(0)->attributes; + if (is_object($alternate)) + foreach($alternate AS $attributes) if ($attributes->name == "href") $author["author-link"] = $attributes->textContent; + if ($author["author-link"] == "") + $author["author-link"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue; + if ($author["author-link"] == "") { - $alternate = $xpath->query("atom:link[@rel='alternate']")->item(0)->attributes; - if (is_object($alternate)) - foreach($alternate AS $attributes) + $self = $xpath->query("atom:link[@rel='self']")->item(0)->attributes; + if (is_object($self)) + foreach($self AS $attributes) if ($attributes->name == "href") $author["author-link"] = $attributes->textContent; } - if ($author["author-link"] == "") - $author["author-link"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue; if ($author["author-link"] == "") $author["author-link"] = $xpath->evaluate('/atom:feed/atom:id/text()')->item(0)->nodeValue; @@ -65,6 +67,14 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { if ($author["author-name"] == "") $author["author-name"] = $xpath->evaluate('/atom:feed/atom:author/atom:name/text()')->item(0)->nodeValue; + $value = $xpath->evaluate('atom:author/poco:displayName/text()')->item(0)->nodeValue; + if ($value != "") + $author["author-name"] = $value; + + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()')->item(0)->nodeValue; + if ($value != "") + $author["author-nick"] = $value; + $author["edited"] = $author["created"] = $xpath->query('/atom:feed/atom:updated/text()')->item(0)->nodeValue; $author["app"] = $xpath->evaluate('/atom:feed/atom:generator/text()')->item(0)->nodeValue; diff --git a/library/simplepie/LICENSE.txt b/library/simplepie/LICENSE.txt deleted file mode 100644 index a822a4bd98..0000000000 --- a/library/simplepie/LICENSE.txt +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2004-2007, Ryan Parman and Geoffrey Sneddon. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are -permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - - * Neither the name of the SimplePie Team nor the names of its contributors may be used - to endorse or promote products derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS -OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS -AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/library/simplepie/README.markdown b/library/simplepie/README.markdown deleted file mode 100644 index e5ca021ced..0000000000 --- a/library/simplepie/README.markdown +++ /dev/null @@ -1,53 +0,0 @@ -# SimplePie - -## Authors and contributors - -* [Ryan Parman](http://ryanparman.com) -* [Geoffrey Sneddon](http://gsnedders.com) -* [Ryan McCue](http://ryanmccue.info) -* [Michael Shipley](http://michaelpshipley.com) -* [Steve Minutillo](http://minutillo.com/steve/) - - -## License - -[New BSD license](http://www.opensource.org/licenses/bsd-license.php) - - -## Project status - -SimplePie is currently maintained by Ryan McCue. - -At the moment, there isn't a lot of active development happening. If the community decides that SimplePie is a valuable tool, then the community will come together to maintain it into the future. - -If you're interested in getting involved with SimplePie, please get in touch with Ryan McCue. - - -## What comes in the package? - -1. `simplepie.inc` - The SimplePie library. This is all that's required for your pages. -2. `README.markdown` - This document. -3. `LICENSE.txt` - A copy of the BSD license. -4. `compatibility_test/` - The SimplePie compatibility test that checks your server for required settings. -5. `demo/` - A basic feed reader demo that shows off some of SimplePie's more noticable features. -6. `idn/` - A third-party library that SimplePie can optionally use to understand Internationalized Domain Names (IDNs). -7. `test/` - SimplePie's unit test suite. - - -## To start the demo - -1. Upload this package to your webserver. -2. Make sure that the cache folder inside of the demo folder is server-writable. -3. Navigate your browser to the demo folder. - - -## Need support? - -For further setup and install documentation, function references, etc., visit: -[http://simplepie.org/wiki/](http://simplepie.org/wiki/) - -For bug reports and feature requests, visit: -[http://github.com/rmccue/simplepie/issues](http://github.com/rmccue/simplepie/issues) - -Support mailing list -- powered by users, for users. -[http://tech.groups.yahoo.com/group/simplepie-support/](http://tech.groups.yahoo.com/group/simplepie-support/) diff --git a/library/simplepie/compatibility_test/COMPATIBILITY README.txt b/library/simplepie/compatibility_test/COMPATIBILITY README.txt deleted file mode 100644 index 5b24989927..0000000000 --- a/library/simplepie/compatibility_test/COMPATIBILITY README.txt +++ /dev/null @@ -1,7 +0,0 @@ -SIMPLEPIE COMPATIBILITY TEST - -1) Upload sp_compatibility_test.php to the web-accessible root of your website. -For example, if your website is www.example.com, upload it so that you can get -to it at www.example.com/sp_compatibility_test.php - -2) Open your web browser and go to the page you just uploaded. \ No newline at end of file diff --git a/library/simplepie/compatibility_test/sp_compatibility_test.php b/library/simplepie/compatibility_test/sp_compatibility_test.php deleted file mode 100644 index a7a7f5fde2..0000000000 --- a/library/simplepie/compatibility_test/sp_compatibility_test.php +++ /dev/null @@ -1,330 +0,0 @@ -<?php -if (isset($_GET['logopng'])) -{ - $data='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'; - header('Content-type: image/png'); - echo base64_decode($data); - exit; -} -else if (isset($_GET['background'])) -{ - $data='R0lGODlhMAEeAeYAAP///8ni6cTf5+72+PD3+c3k6+nz9ufy9ev099Pn7bnZ48bg6LfY4uHv8/r8/f3+/v7//7bX4cjh6fj7/Mvj6vz9/rva4+z19/X6+/f7/Pn8/fb6+7jZ4vv9/bra473b5Lzb5LXX4b7c5b/c5e31+NTo7tvs8dfq7/H3+bjY4tnq79Hm7c/l69nr8PL4+t7t8sDd5cLe5uPw9Nvr8Mri6fP4+tLn7er099Hm7O/2+dDm7OTw9OXx9Nbp7tbp7+Lv8+Du8szj6sXg57bY4d3s8djq7+jz9tzs8cPe58fh6M7k68Pf5+by9fz+/sDd5vT5+vT5+97t8bbY4trr8P7+/v7+/+Pw8+Xx9dXo7sHe5vH4+fP5+sHd5t/u8s/l7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAwAR4BAAf/gCGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKNEaWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExbBDyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7KUuHi4+Tl5ufo6err7O3u7/Dx8vP09fb34wz6+/z9/v8AAwocSLCgwYMIEypcyLChw4cQI0qcSLGixYsYM2osmKKjx48gQ4ocSbKkyZMoU6pcybKly5cwY8qc+ZGDzZs4c+rcybOnz59AgwodSrSo0aNIkypdyrSp06dQo0qdSrWq1aAKsmrdyrWr169gw4odS7as2bNo06pdy7at27dw/+PKnUu3rt27ePOS9cC3r9+/gAMLHky4sOHDiBMrXsy4sePHkCNLnky5suXLmDNr3sz5sIXPoEOLHk26tOnTqFOrXs26tevXsGPLnk27tu3buHPr3s27t+/fqkEIH068uPHjyJMrX868ufPn0KNLn069uvXr2LNr3869u/fv4MOLb/6hvPnz6NOrX8++vfv38OPLn0+/vv37+PPr38+/v///AAYo4IAEFgifCAgmqOCCDDbo4IMQRijhhBRWaOGFGGao4YYcdujhhyCGKOKIJJZo4okoTjjCiiy26OKLMMYo44w01mjjjTjmqOOOPPbo449ABinkkEQWaeSRSCap5P+SNsLg5JNQRinllFRWaeWVWGap5ZZcdunll2CGKeaYZELpxJlopqnmmmy26eabcMYp55x01mnnnXjmqeeefPaZJheABirooIQWauihiCaq6KKMNuroo5BGKumklFZqqaBZZKrpppx26umnoIYq6qiklmrqqaimquqqrLbq6qubxiDrrLTWauutuOaq66689urrr8AGK+ywxBZr7LHI0orEssw26+yz0EYr7bTUVmvttdhmq+223Hbr7bfghtvsEuSWa+656Kar7rrstuvuu/DGK++89NZr77345quvuQL06++/AAcs8MAEF2zwwQgnrPDCDDfs8MMQRyzxxBRXbPH/xRhnrPHGHHeMsBAghyzyyCSXbPLJKKes8sost+zyyzDHLPPMNNdss8gL5Kzzzjz37PPPQAct9NBEF2300UgnrfTSTDft9NNQRy311FRXbfXVWGdNdBJcd+3112CHLfbYZJdt9tlop6322my37fbbcMctt9cS1G333XjnrffefPft99+ABy744IQXbvjhiCeu+OKMN+7445BHLvnklFcOeACYZ6755px37vnnoIcu+uikl2766ainrvrqrLfu+uuwxy777LTXbvvtuI9Ow+689+7778AHL/zwxBdv/PHIJ6/88sw37/zz0EffOwXUV2/99dhnr/323Hfv/ffghy/+//jkl2/++einr/767Lfv/vvwxy///PR/H8T9+Oev//789+///wAMoAAHSMACGvCACEygAhfIwAbmrwAQjKAEJ0jBClrwghjMoAY3yMEOevCDIAyhCEdIwhKa8IQoTKEKV8jCFrrwhTDcoBJmSMMa2vCGOMyhDnfIwx768IdADKIQh0jEIhrxiEhMYg1ZwMQmOvGJUIyiFKdIxSpa8YpYzKIWt8jFLnrxi2AMoxid6IUymvGMaEyjGtfIxja68Y1wjKMc50jHOtrxjnjMox73eEYd+PGPgAykIAdJyEIa8pCITKQiF8nIRjrykZCMpCQnSUlA4uCSmMykJjfJyU568v+ToAylKEdJylKa8pSoTKUqV8nKVmZyBbCMpSxnScta2vKWuMylLnfJy1768pfADKYwh0nMYhpTljZIpjKXycxmOvOZ0IymNKdJzWpa85rYzKY2t8nNbnrzm8tMgDjHSc5ymvOc6EynOtfJzna6853wjKc850nPetrznvjMpz73yc9++vOfAA2oQNtZgoIa9KAITahCF8rQhjr0oRCNqEQnStGKWvSiGM2oRjd6UCx49KMgDalIR0rSkpr0pChNqUpXytKWuvSlMI2pTGdKU5D24KY4zalOd8rTnvr0p0ANqlCHStSiGvWoSE2qUpfK1Kbm1AdQjapUp0rVqlr1qlj/zapWt8rVrnr1q2ANq1jHStaymlWqJ0irWtfK1ra69a1wjatc50rXutr1rnjNq173yte++vWvay2CYAdL2MIa9rCITaxiF8vYxjr2sZCNrGQnS9nKWvaymCWsCjbL2c569rOgDa1oR0va0pr2tKhNrWpXy9rWuva1sI1tZ1tA29ra9ra4za1ud8vb3vr2t8ANrnCHS9ziGve4yE2ucm07heY697nQja50p0vd6lr3utjNrna3y93ueve74A2veMf73BmY97zoTa9618ve9rr3vfCNr3znS9/62ve++M2vfvfLX/Sa4L8ADrCAB0zgAhv4wAhOsIIXzOAGO/jBEI6w/4QnTOEKB/gIGM6whjfM4Q57+MMgDrGIR0ziEpv4xChOsYpXzOIWu1jDRIixjGdM4xrb+MY4zrGOd8zjHvv4x0AOspCHTOQiG/nIM46CkpfM5CY7+clQjrKUp0zlKlv5yljOspa3zOUue/nLYGbyC8ZM5jKb+cxoTrOa18zmNrv5zXCOs5znTOc62/nOeM5zmbvA5z77+c+ADrSgB03oQhv60IhOtKIXzehGO/rRkI60pP0MhEpb+tKYzrSmN83pTnv606AOtahHTepSm/rUqE61qld96Qa4+tWwjrWsZ03rWtv61rjOta53zete+/rXwA62sIdN7GIb+9jITrayl//N7GY7O9c/iLa0p03talv72tjOtra3ze1ue/vb4A63uMdN7nKb+9zTtoK6183udrv73fCOt7znTe962/ve+M63vvfN7377+98AZ7cMBk7wghv84AhPuMIXzvCGO/zhEI+4xCdO8Ypb/OIYz3jBd8Dxjnv84yAPuchHTvKSm/zkKE+5ylfO8pa7/OUwj7nMPc6Dmtv85jjPuc53zvOe+/znQA+60IdO9KIb/ehIT7rSl37zKzj96VCPutSnTvWqW/3qWM+61rfO9a57/etgD7vYx052qDPh7GhPu9rXzva2u/3tcI+73OdO97rb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'; - header('Content-type: image/gif'); - echo base64_decode($data); - exit; -} - -$php_ok = (function_exists('version_compare') && version_compare(phpversion(), '4.3.0', '>=')); -$pcre_ok = extension_loaded('pcre'); -$curl_ok = function_exists('curl_exec'); -$zlib_ok = extension_loaded('zlib'); -$mbstring_ok = extension_loaded('mbstring'); -$iconv_ok = extension_loaded('iconv'); -if (extension_loaded('xmlreader')) -{ - $xml_ok = true; -} -elseif (extension_loaded('xml')) -{ - $parser_check = xml_parser_create(); - xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); - xml_parser_free($parser_check); - $xml_ok = isset($values[0]['value']); -} -else -{ - $xml_ok = false; -} - -header('Content-type: text/html; charset=UTF-8'); - -?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> - -<html lang="en"> -<head> -<title>SimplePie: Server Compatibility Test 1.2</title> - -<style type="text/css"> -body { - font:14px/1.4em "Lucida Grande", Verdana, Arial, Helvetica, Clean, Sans, sans-serif; - letter-spacing:0px; - color:#333; - margin:0; - padding:0; - background:#fff url(<?php echo pathinfo(__FILE__, PATHINFO_BASENAME); ?>?background) repeat-x top left; -} - -div#site { - width:550px; - margin:20px auto 0 auto; -} - -a { - color:#000; - text-decoration:underline; - padding:0 1px; -} - -a:hover { - color:#fff; - background-color:#333; - text-decoration:none; - padding:0 1px; -} - -p { - margin:0; - padding:5px 0; -} - -em { - font-style:normal; - background-color:#ffc; -} - -ul, ol { - margin:10px 0 10px 20px; - padding:0 0 0 15px; -} - -ul li, ol li { - margin:0 0 7px 0; - padding:0 0 0 3px; -} - -h2 { - font-size:18px; - padding:0; - margin:30px 0 10px 0; -} - -h3 { - font-size:16px; - padding:0; - margin:20px 0 5px 0; -} - -h4 { - font-size:14px; - padding:0; - margin:15px 0 5px 0; -} - -code { - font-size:1.1em; - background-color:#f3f3ff; - color:#000; -} - -em strong { - text-transform: uppercase; -} - -table#chart { - border-collapse:collapse; -} - -table#chart th { - background-color:#eee; - padding:2px 3px; - border:1px solid #fff; -} - -table#chart td { - text-align:center; - padding:2px 3px; - border:1px solid #eee; -} - -table#chart tr.enabled td { - /* Leave this alone */ -} - -table#chart tr.disabled td, -table#chart tr.disabled td a { - color:#999; - font-style:italic; -} - -table#chart tr.disabled td a { - text-decoration:underline; -} - -div.chunk { - margin:20px 0 0 0; - padding:0 0 10px 0; - border-bottom:1px solid #ccc; -} - -.footnote, -.footnote a { - font:10px/12px verdana, sans-serif; - color:#aaa; -} - -.footnote em { - background-color:transparent; - font-style:italic; -} -</style> - -<script type="text/javascript"> -// Sleight - Alpha transparency PNG's in Internet Explorer 5.5/6.0 -// (c) 2001, Aaron Boodman; http://www.youngpup.net - -if (navigator.platform == "Win32" && navigator.appName == "Microsoft Internet Explorer" && window.attachEvent) { - document.writeln('<style type="text/css">img, input.image { visibility:hidden; } </style>'); - window.attachEvent("onload", fnLoadPngs); -} - -function fnLoadPngs() { - var rslt = navigator.appVersion.match(/MSIE (\d+\.\d+)/, ''); - var itsAllGood = (rslt != null && Number(rslt[1]) >= 5.5); - - for (var i = document.images.length - 1, img = null; (img = document.images[i]); i--) { - if (itsAllGood && img.src.match(/\png$/i) != null) { - var src = img.src; - var div = document.createElement("DIV"); - div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizing='scale')"; - div.style.width = img.width + "px"; - div.style.height = img.height + "px"; - img.replaceNode(div); - } - img.style.visibility = "visible"; - } -} -</script> - -</head> - -<body> - -<div id="site"> - <div id="content"> - - <div class="chunk"> - <h2 style="text-align:center;"><img src="<?php echo pathinfo(__FILE__, PATHINFO_BASENAME); ?>?logopng" alt="SimplePie Compatibility Test" title="SimplePie Compatibility Test" /></h2> - <table cellpadding="0" cellspacing="0" border="0" width="100%" id="chart"> - <thead> - <tr> - <th>Test</th> - <th>Should Be</th> - <th>What You Have</th> - </tr> - </thead> - <tbody> - <tr class="<?php echo ($php_ok) ? 'enabled' : 'disabled'; ?>"> - <td>PHP¹</td> - <td>4.3.0 or higher</td> - <td><?php echo phpversion(); ?></td> - </tr> - <tr class="<?php echo ($xml_ok) ? 'enabled, and sane' : 'disabled, or broken'; ?>"> - <td><a href="http://php.net/xml">XML</a></td> - <td>Enabled</td> - <td><?php echo ($xml_ok) ? 'Enabled, and sane' : 'Disabled, or broken'; ?></td> - </tr> - <tr class="<?php echo ($pcre_ok) ? 'enabled' : 'disabled'; ?>"> - <td><a href="http://php.net/pcre">PCRE</a>²</td> - <td>Enabled</td> - <td><?php echo ($pcre_ok) ? 'Enabled' : 'Disabled'; ?></td> - </tr> - <tr class="<?php echo ($curl_ok) ? 'enabled' : 'disabled'; ?>"> - <td><a href="http://php.net/curl">cURL</a></td> - <td>Enabled</td> - <td><?php echo (extension_loaded('curl')) ? 'Enabled' : 'Disabled'; ?></td> - </tr> - <tr class="<?php echo ($zlib_ok) ? 'enabled' : 'disabled'; ?>"> - <td><a href="http://php.net/zlib">Zlib</a></td> - <td>Enabled</td> - <td><?php echo ($zlib_ok) ? 'Enabled' : 'Disabled'; ?></td> - </tr> - <tr class="<?php echo ($mbstring_ok) ? 'enabled' : 'disabled'; ?>"> - <td><a href="http://php.net/mbstring">mbstring</a></td> - <td>Enabled</td> - <td><?php echo ($mbstring_ok) ? 'Enabled' : 'Disabled'; ?></td> - </tr> - <tr class="<?php echo ($iconv_ok) ? 'enabled' : 'disabled'; ?>"> - <td><a href="http://php.net/iconv">iconv</a></td> - <td>Enabled</td> - <td><?php echo ($iconv_ok) ? 'Enabled' : 'Disabled'; ?></td> - </tr> - </tbody> - </table> - </div> - - <div class="chunk"> - <h3>What does this mean?</h3> - <ol> - <?php if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $iconv_ok && $curl_ok && $zlib_ok): ?> - <li><em>You have everything you need to run SimplePie properly! Congratulations!</em></li> - <?php else: ?> - <?php if ($php_ok): ?> - <li><strong>PHP:</strong> You are running a supported version of PHP. <em>No problems here.</em></li> - <?php if ($xml_ok): ?> - <li><strong>XML:</strong> You have XMLReader support or a version of XML support that isn't broken installed. <em>No problems here.</em></li> - <?php if ($pcre_ok): ?> - <li><strong>PCRE:</strong> You have PCRE support installed. <em>No problems here.</em></li> - <?php if ($curl_ok): ?> - <li><strong>cURL:</strong> You have <code>cURL</code> support installed. <em>No problems here.</em></li> - <?php else: ?> - <li><strong>cURL:</strong> The <code>cURL</code> extension is not available. SimplePie will use <code>fsockopen()</code> instead.</li> - <?php endif; ?> - - <?php if ($zlib_ok): ?> - <li><strong>Zlib:</strong> You have <code>Zlib</code> enabled. This allows SimplePie to support GZIP-encoded feeds. <em>No problems here.</em></li> - <?php else: ?> - <li><strong>Zlib:</strong> The <code>Zlib</code> extension is not available. SimplePie will ignore any GZIP-encoding, and instead handle feeds as uncompressed text.</li> - <?php endif; ?> - - <?php if ($mbstring_ok && $iconv_ok): ?> - <li><strong>mbstring and iconv:</strong> You have both <code>mbstring</code> and <code>iconv</code> installed! This will allow SimplePie to handle the greatest number of languages. Check the <a href="http://simplepie.org/wiki/faq/supported_character_encodings">Supported Character Encodings</a> chart to see what's supported on your webhost.</li> - <?php elseif ($mbstring_ok): ?> - <li><strong>mbstring:</strong> <code>mbstring</code> is installed, but <code>iconv</code> is not. Check the <a href="http://simplepie.org/wiki/faq/supported_character_encodings">Supported Character Encodings</a> chart to see what's supported on your webhost.</li> - <?php elseif ($iconv_ok): ?> - <li><strong>iconv:</strong> <code>iconv</code> is installed, but <code>mbstring</code> is not. Check the <a href="http://simplepie.org/wiki/faq/supported_character_encodings">Supported Character Encodings</a> chart to see what's supported on your webhost.</li> - <?php else: ?> - <li><strong>mbstring and iconv:</strong> <em>You do not have either of the extensions installed.</em> This will significantly impair your ability to read non-English feeds, as well as even some English ones. Check the <a href="http://simplepie.org/wiki/faq/supported_character_encodings">Supported Character Encodings</a> chart to see what's supported on your webhost.</li> - <?php endif; ?> - <?php else: ?> - <li><strong>PCRE:</strong> Your PHP installation doesn't support Perl-Compatible Regular Expressions. <em>SimplePie is a no-go at the moment.</em></li> - <?php endif; ?> - <?php else: ?> - <li><strong>XML:</strong> Your PHP installation doesn't support XML parsing. <em>SimplePie is a no-go at the moment.</em></li> - <?php endif; ?> - <?php else: ?> - <li><strong>PHP:</strong> You are running an unsupported version of PHP. <em>SimplePie is a no-go at the moment.</em></li> - <?php endif; ?> - <?php endif; ?> - </ol> - </div> - - <div class="chunk"> - <?php if ($php_ok && $xml_ok && $pcre_ok && $mbstring_ok && $iconv_ok) { ?> - <h3>Bottom Line: Yes, you can!</h3> - <p><em>Your webhost has its act together!</em></p> - <p>You can download the latest version of SimplePie from <a href="http://simplepie.org/downloads/">SimplePie.org</a> and install it by <a href="http://simplepie.org/wiki/setup/start">following the instructions</a>. You can find example uses with <a href="http://simplepie.org/ideas/">SimplePie Ideas</a>.</p> - <p>Take the time to read <a href="http://simplepie.org/wiki/setup/start">Requirements and Getting Started</a> to make sure you're prepared to use SimplePie. No seriously, read them.</p> - <p class="footnote"><em><strong>Note</strong></em>: Passing this test does not guarantee that SimplePie will run on your webhost — it only ensures that the basic requirements have been addressed.</p> - <?php } else if ($php_ok && $xml_ok && $pcre_ok) { ?> - <h3>Bottom Line: Yes, you can!</h3> - <p><em>For most feeds, it'll run with no problems.</em> There are <a href="http://simplepie.org/wiki/faq/supported_character_encodings">certain languages</a> that you might have a hard time with though.</p> - <p>You can download the latest version of SimplePie from <a href="http://simplepie.org/downloads/">SimplePie.org</a> and install it by <a href="http://simplepie.org/wiki/setup/start">following the instructions</a>. You can find example uses with <a href="http://simplepie.org/ideas/">SimplePie Ideas</a>.</p> - <p>Take the time to read <a href="http://simplepie.org/wiki/setup/start">Requirements and Getting Started</a> to make sure you're prepared to use SimplePie. No seriously, read them.</p> - <p class="footnote"><em><strong>Note</strong></em>: Passing this test does not guarantee that SimplePie will run on your webhost — it only ensures that the basic requirements have been addressed.</p> - <?php } else { ?> - <h3>Bottom Line: We're sorry…</h3> - <p><em>Your webhost does not support the minimum requirements for SimplePie.</em> It may be a good idea to contact your webhost, and ask them to install a more recent version of PHP as well as the <code>xmlreader</code>, <code>xml</code>, <code>mbstring</code>, <code>iconv</code>, <code>curl</code>, and <code>zlib</code> extensions.</p> - <?php } ?> - </div> - - <div class="chunk"> - <p class="footnote">¹ — SimplePie 2 will not support PHP 4.x. The core PHP team has discontinued PHP 4.x patches and support. <a href="http://simplepie.org/blog/2007/07/13/simplepie-is-going-php5-only/">Read the announcement.</a></p> - <p class="footnote">² — Some recent versions of the PCRE (PERL-Compatible Regular Expression) engine compiled into PHP have been buggy, and are the source of PHP segmentation faults (e.g. crashes) which cause random things like blank, white screens. Check the <a href="http://simplepie.org/support/">Support Forums</a> for the latest information on patches and ongoing fixes.</p> - </div> - - </div> - -</div> - -</body> -</html> \ No newline at end of file diff --git a/library/simplepie/create.php b/library/simplepie/create.php deleted file mode 100644 index 908ed182bd..0000000000 --- a/library/simplepie/create.php +++ /dev/null @@ -1,178 +0,0 @@ -<?php - -require_once 'simplepie.inc'; - -function normalize_character_set($charset) -{ - return strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset)); -} - -function build_character_set_list() -{ - $file = new SimplePie_File('http://www.iana.org/assignments/character-sets'); - if (!$file->success && !($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) - { - return false; - } - else - { - $data = explode("\n", $file->body); - unset($file); - - foreach ($data as $line) - { - // New character set - if (substr($line, 0, 5) === 'Name:') - { - // If we already have one, push it on to the array - if (isset($aliases)) - { - for ($i = 0, $count = count($aliases); $i < $count; $i++) - { - $aliases[$i] = normalize_character_set($aliases[$i]); - } - $charsets[$preferred] = array_unique($aliases); - natsort($charsets[$preferred]); - } - - $start = 5 + strspn($line, "\x09\x0A\x0B\xC\x0D\x20", 5); - $chars = strcspn($line, "\x09\x0A\x0B\xC\x0D\x20", $start); - $aliases = array(substr($line, $start, $chars)); - $preferred = end($aliases); - } - // Another alias - elseif(substr($line, 0, 6) === 'Alias:') - { - $start = 7 + strspn($line, "\x09\x0A\x0B\xC\x0D\x20", 7); - $chars = strcspn($line, "\x09\x0A\x0B\xC\x0D\x20", $start); - $aliases[] = substr($line, $start, $chars); - - if (end($aliases) === 'None') - { - array_pop($aliases); - } - elseif (substr($line, 7 + $chars + 1, 21) === '(preferred MIME name)') - { - $preferred = end($aliases); - } - } - } - - // Compatibility replacements - $compat = array( - 'EUC-KR' => 'windows-949', - 'GB2312' => 'GBK', - 'GB_2312-80' => 'GBK', - 'ISO-8859-1' => 'windows-1252', - 'ISO-8859-9' => 'windows-1254', - 'ISO-8859-11' => 'windows-874', - 'KS_C_5601-1987' => 'windows-949', - 'TIS-620' => 'windows-874', - //'US-ASCII' => 'windows-1252', - 'x-x-big5' => 'Big5', - ); - - foreach ($compat as $real => $replace) - { - if (isset($charsets[$real]) && isset($charsets[$replace])) - { - $charsets[$replace] = array_merge($charsets[$replace], $charsets[$real]); - unset($charsets[$real]); - } - elseif (isset($charsets[$real])) - { - $charsets[$replace] = $charsets[$real]; - $charsets[$replace][] = normalize_character_set($replace); - unset($charsets[$real]); - } - else - { - $charsets[$replace][] = normalize_character_set($real); - } - $charsets[$replace] = array_unique($charsets[$replace]); - natsort($charsets[$replace]); - } - - // Sort it - uksort($charsets, 'strnatcasecmp'); - - // Check that nothing matches more than one - $all = call_user_func_array('array_merge', $charsets); - $all_count = array_count_values($all); - if (max($all_count) > 1) - { - echo "Duplicated charsets:\n"; - foreach ($all_count as $charset => $count) - { - if ($count > 1) - { - echo "$charset\n"; - } - } - } - - // And we're done! - return $charsets; - } -} - -function charset($charset) -{ - $normalized_charset = normalize_character_set($charset); - if ($charsets = build_character_set_list()) - { - foreach ($charsets as $preferred => $aliases) - { - if (in_array($normalized_charset, $aliases)) - { - return $preferred; - } - } - return $charset; - } - else - { - return false; - } -} - -function build_function() -{ - if ($charsets = build_character_set_list()) - { - $return = <<<EOF -function charset(\$charset) -{ - // Normalization from UTS #22 - switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\\1', \$charset))) - { - -EOF; - foreach ($charsets as $preferred => $aliases) - { - foreach ($aliases as $alias) - { - $return .= "\t\tcase " . var_export($alias, true) . ":\n"; - } - $return .= "\t\t\treturn " . var_export($preferred, true) . ";\n\n"; - } - $return .= <<<EOF - default: - return \$charset; - } -} -EOF; - return $return; - } - else - { - return false; - } -} - -if (php_sapi_name() === 'cli' && realpath($_SERVER['argv'][0]) === __FILE__) -{ - echo build_function(); -} - -?> \ No newline at end of file diff --git a/library/simplepie/db.sql b/library/simplepie/db.sql deleted file mode 100644 index 13f504c214..0000000000 --- a/library/simplepie/db.sql +++ /dev/null @@ -1,38 +0,0 @@ -/* SQLite */ -CREATE TABLE cache_data ( - id TEXT NOT NULL, - items SMALLINT NOT NULL DEFAULT 0, - data BLOB NOT NULL, - mtime INTEGER UNSIGNED NOT NULL -); -CREATE UNIQUE INDEX id ON cache_data(id); - -CREATE TABLE items ( - feed_id TEXT NOT NULL, - id TEXT NOT NULL, - data TEXT NOT NULL, - posted INTEGER UNSIGNED NOT NULL -); -CREATE INDEX feed_id ON items(feed_id); - - -/* MySQL */ -CREATE TABLE `cache_data` ( - `id` TEXT CHARACTER SET utf8 NOT NULL, - `items` SMALLINT NOT NULL DEFAULT 0, - `data` BLOB NOT NULL, - `mtime` INT UNSIGNED NOT NULL, - UNIQUE ( - `id`(125) - ) -); - -CREATE TABLE `items` ( - `feed_id` TEXT CHARACTER SET utf8 NOT NULL, - `id` TEXT CHARACTER SET utf8 NOT NULL, - `data` TEXT CHARACTER SET utf8 NOT NULL, - `posted` INT UNSIGNED NOT NULL, - INDEX `feed_id` ( - `feed_id`(125) - ) -); \ No newline at end of file diff --git a/library/simplepie/demo/cli_test.php b/library/simplepie/demo/cli_test.php deleted file mode 100644 index ec933c5ad7..0000000000 --- a/library/simplepie/demo/cli_test.php +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/php -<?php -include_once('../simplepie.inc'); - -// Parse it -$feed = new SimplePie(); -if (isset($argv[1]) && $argv[1] !== '') -{ - $feed->set_feed_url($argv[1]); - $feed->enable_cache(false); - $feed->init(); -} - -$items = $feed->get_items(); - -foreach ($items as $item) -{ - echo $item->get_title() . "\n"; -} - -var_dump($feed->get_item_quantity()); - -?> \ No newline at end of file diff --git a/library/simplepie/demo/for_the_demo/alternate_favicon.png b/library/simplepie/demo/for_the_demo/alternate_favicon.png deleted file mode 100644 index 063fb280549db7a68a47cce157d680ed7af1a8a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28621 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 diff --git a/library/simplepie/demo/for_the_demo/background_blockquote.png b/library/simplepie/demo/for_the_demo/background_blockquote.png deleted file mode 100644 index 8267e23a23575eb148f8b8e3305732e6f09870de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27353 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 diff --git a/library/simplepie/demo/for_the_demo/background_menuitem.gif b/library/simplepie/demo/for_the_demo/background_menuitem.gif deleted file mode 100644 index fa765d670e739793c2ec69e16e6be33618f5425f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 533 zcmV+w0_y!oNk%w1VJrb00K@<Q|NsB~{{H>_{rvp=`}_O)`uh3#`S|$w_xJbq_V)Gl z_4M@g^Yioa^78TV@$m5Q@9*#K?(XgF?d<IA>+9?4>gws~>FDU_=jZ3<=H}()<>ch# z<KyGv;^F|AGXMYpA^8LV2LS&7EC2ui04xC<000I5ARvxpX`X1Ru59bRa4gSsZQppV z?|kq7z@TtQEE<o<q;kn@I-k&}bV{vSuh^`1%k6r<;IMd1E}PHjw0g}J2j1{^drn`* z?{I;B&llAHfHQb_eS8Clh=>G>j0Ay?kSK!zdx?sTj0KvTng*Vqk)eT;0hWlIou8@) ztgQ&Iun3~Fb9sG@sI9EAunE4u2@1l)!nDR{c$S-=vA@K^3(?XG4As^R*bT?qV|S(l zss_Hq*4NqC4(aL-?ClTS@Lq$Mx(CGB>+b9j{QVLC03`^FN03V&RI#S1O9wEZz=skh zNSsKaV#R_OwIuoktem@u6DL?4S)ruK3Kmk5s?5l;%0^GX&h?vkQl-iZICJ8}z_TZY zmO!Dr6z9uYyN4!o`t+Hhsnez!qDl=)l?ft_MXRxcX!EJmuTeLM9XpnT*{W!fT=g^z z+f0cno02VSwys^X9rEf;%Qr^>Egk|39!$8f;lqd%D_+dFvE#>(BTJr4xw7TUm@{kM X%(=7Y&!9t#9!<Km>C>o9hX4ROX5}q3 diff --git a/library/simplepie/demo/for_the_demo/background_menuitem_off.gif b/library/simplepie/demo/for_the_demo/background_menuitem_off.gif deleted file mode 100644 index 236cf406dc2528b538ed078628f9f95e3240f9fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 533 zcmV+w0_y!oNk%w1VJrb00K@<Q|NsB~{{H>_{rvp=`}_O)`uh3#`S|$w_xJbq_V)Gl z_4M@g^Yioa^78TV@$m5Q@9*#K?(XgF?d<IA>+9?4>gws~>FDU_=jZ3<=H}()<>ch# z<KyGv;^H$iGXMYpA^8LV2LS&7EC2ui04xC<000I5ARvxpX`X1Ru59bRa4gSsZQppV z?|kq7z@TtQEE<o<q;kn@I-k&}bV{vSuh^`1%k6r<;IMd1E}PHjw0g}J2j1{^drn`* z?{I;B&llAHfHQb_eS8Clh=>G>j0Ay?kSK!zdx?sTj0KvTng*Vqk)eT;0hWlIou8@) ztgQ&Iun3~Fb9sG@sI9EAunE4u2@1l)!nDR{c$S-=vA@K^3(?XG4As^R*bT?qV|S(l zss_Hq*4NqC4(aL-?ClTS@Lq$Mx(CGB>+b9j{QVLC03`^FN03V&RI#S1O9wEZz=skh zNSsKaV#R_OwIuoktem@u6DL?4S)ruK3Kmk5s?5l;%0^GX&h?vkQl-iZICJ8}z_TZY zmO!Dr6z9uYyN4!o`t+Hhsnez!qDl=)l?ft_MXRxcX!EJmuTeLM9XpnT*{W!fT=g^z z+f0cno02VSwys^X9rEf;%Qr^>Egk|39!$8f;lqd%D_+dFvE#>(BTJr4xw7TUm@{kM X%(=7Y&!9t#9!<Km>C>o9hX4RO5uq&s diff --git a/library/simplepie/demo/for_the_demo/background_menuitem_shadow.gif b/library/simplepie/demo/for_the_demo/background_menuitem_shadow.gif deleted file mode 100644 index 95cfb820d47f717cc9ddd31d626d497e23f1a38a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 250 zcmV<W00sX?Nk%w1VG00v0J8u9_4W1c?(XgF?d<IA>+9?4>gws~>FDU_=jZ3<=H}() z<>ch#;^N}q;Naii-`(BaA^8LV2LS&7EC2ui015zj000E1@U2H>RW3WMn{UYnG9*Hl z=drAdx*AEGz;S`xh>aV3?-RhFFgPe02Y`SwkX$;ON+^`MoJyZntTW4{cCB5nc&v?v zuir9wYfih{V>Eobj-T)IX<1zFtM_|ye1C6bcy@V*b##Pqeua99jf0SEiI<3(i-D4r zn2es9laZK@j-!^Mp{9nVsIFn0u&1G*w6~PFq^i1|bF#d-!n>@3#)r1UzG4voJBQbb A?f?J) diff --git a/library/simplepie/demo/for_the_demo/favicons/alternate.png b/library/simplepie/demo/for_the_demo/favicons/alternate.png deleted file mode 100644 index 063fb280549db7a68a47cce157d680ed7af1a8a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28621 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 diff --git a/library/simplepie/demo/for_the_demo/favicons/blinklist.png b/library/simplepie/demo/for_the_demo/favicons/blinklist.png deleted file mode 100644 index 53200b3c62c7a00e21fe30af69961eed389ff41f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4377 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 Tm7TM^00000NkvXXu0mjfB^P<7 diff --git a/library/simplepie/demo/for_the_demo/favicons/blogmarks.png b/library/simplepie/demo/for_the_demo/favicons/blogmarks.png deleted file mode 100644 index c5372614a4a623d418be15ececf2768a87f70f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3823 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 diff --git a/library/simplepie/demo/for_the_demo/favicons/delicious.png b/library/simplepie/demo/for_the_demo/favicons/delicious.png deleted file mode 100644 index 2e6021d26e2d699ace681e80b4dff96eee0a5830..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3739 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 FV1i(EKivQT diff --git a/library/simplepie/demo/for_the_demo/favicons/digg.png b/library/simplepie/demo/for_the_demo/favicons/digg.png deleted file mode 100644 index 3aa96770e80ac18446c0254f37af3d75740cc415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4004 zcmV;V4_okwP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000gvX+uL$Nkc;* zP;zf(X>4Tx0C=30S9dto58wWL&oqzYI96n@aI9mmjL3}ak-f{F8Ie(u%#4&IqmV=> zQc@v>sH`Lv2~o+ZRK|J!=y~7YAMf>C&-LDa-JkoqKJ%Z?bpx>Rc!h@tqW~ZzG&<5| zzk!gGvx^XZ5^%r?XdnUA%O@&a-`d&&{v&=)00IE;lHwH}9_;qs17BC{+MnT6iqfks z)%eeO002OUbaHk9Kx6@c9PpRx0RY+iFSi2#a$IzHGyoC^0A!y)FJAy82>@gx?Q9GI zkU9V$1pMV406_5m%Lf2}5bF~V4S-Am;7FmqL7@QnRREl>uV0i806`W2e0`&QLI4Q) z03a6<66Om)=m7vJpYX_N0KyUgP@J4yg#M0vhzUS-4**X7A1zt}ptTY}(D@%t;wk`_ z4Z!t_|7bKDKmY*Y8j13!Dggih!eRh`ou$)h5&-x@fb9Z0eY1*A->wEg&j9oU`@}@X z{`+1CG7RwF;=eop8)yUo1pr)N0T&T0Vu@6t(5Oh%Dmog2$24GFaNM}J_zs5ij5$ns z%;#B}Sw{&QL>2Zp(ja*^X9(9Qk3MfBA0K~_0A4U(m?TmxMij4-q)4?%E6EH{tmI}C z4l6EFgOpcPW7W|b37RzRNS*I{<Mfsd!VEtcIT{a|$e31`<1FGVC#-a>Yi$^8{p=n% zNI1qjO}ePMRvg-JckpQQV)gd+Y4Jn+odd21(hln#$qAkcl@1FFzZtO^r4bz&b369y zQHgl}gtElJq^)F1iu>{0)Rwe28R!$@CrwX<W)^4NJw2KIEr*ax$urOQDo8Ipf3~IQ zZt+;j(mA9ITlVez*oAxL=P#yK9KPgGsd1UF3aMJI9<AxFy>un(YG9ptJ+*=V8v5FD z<CE*nO~p6jn_XM9Z}Q*VZk=m;*j{lf{kCU^RwqyA?>iG+&3Cizxp%AHC*J?i)BYgq zq1z*+UR>{Z-}U~a$L0gP1M^RAJxv)j8{!_CAFh8EGom|+jP^WFe4+ee<z>y7*EsL^ zz(m%h-sJjJ{VUID&gsXmkIg8~%)Kd{-9P)|?bUbo?~(V{=RD`x=i5K{EpRT}`{=hw zSnOExS!P*o{^Y!ZU8!HST%~<3|6=fE;cM=i+S=Q1r`F}yU(-%~SN*>Dqw=TQhTz8X zuioDsf1YiL?y%D70Kg7fkO(7)G;#`ANBN=V(P0=orWR|BW5V^}^B8;?^_Ya2xmXxk zQ3NuZAW@OsffUbii9E=;!>z&-#oNA%&TqN9Qea(3Q}~?7Z?Oa7Es`u!NB2y~P-P1! zG<gSwE+t{=DdkV9dTKT5+nSzQZ8|tz$Guneei@h;=I@&~mfRm_(rd<MZf8+uxnLz> z9dYokEt{RKeYL}yla_O;OYb2DH#_$VkJnxT-d;YnzH9#S0Y?IF2CW<s4Gst?3w;*O z6rmd#8dVWJ5<`!ZJ$f)cHK8i8FKH>6B}M7DQ))!o>Gax+hbP{gT+d|95<9J&t$W5g z$2~VLFF8M~z@yOatU(dASgJ&zlvqkX_o-~+eE)^6@|ugK6(=r5R=Qr+uad6jszKMR z)DB&_d9|i4v;J^{)isqyp2k1d-!(nBQQ4f?5_Hp~RjQ4#ZMA*)*45kD9f6%jcSO4| zU5j^z?p^6lyYJOw_(1f*&cmrk?Y+f)@%=83)dx5RXisLI-Wtps3LCb5CO5(|@^y6J zdHsvbmqBBu<5Ck06U&nWQ#G$rr#)Zm&hXD{znPisd|Ui3;=R?J{5;G2hYwE{sy`lI z^jOkf=3b_K8d<r%dggP`7vryDU+HVp-)^s$(4xLu|B(O5`t#Gq<6oD5C;xHXRNW$P zecvA1X{3A8=>Wh4Vqgx(pbJ4G#z-NufYL|RpxMxA=q*etmK9r$qvFQ#2@K*4lZ<&x zmdxzTGc5J22?T341tK%?C;K$%0Y@YG0%tB)I(IxzC~v?nPd*QR*WC^R2L%rb*$CT- z*o#_<8Ht-q7)k0$Y3@;#7LgH<Wu@RL8*+>CvkKFSFO-I;1IqVR+ElNpRjZe2<Y}JK zI<1|dld7A%H$g8-Kght#(0iY!k&Us{esdFJQ!6twb3+R=OM?U2RtDBu2i0tJZ8h!G z>{T389MzmOoT)BquGB*cZVK*79%`OCUYg!&KI*<&etZ431B?QVgNzUB9MKEb4KWEd z3ZsS#MGzylBA243qlaSd#9ocdJ{lYEnP8KsktBYMc<fj5Y|4Y<)v38@ap{LL)KBoA z#GG6_)t_0Lm2)~Md;b}!99+&q?&G|g{L}*XLhZABXMYuq6}Oh;lm?vJS0-{EeSZEz zclo)CF%|ZgsFj4ukCz`-RaU3h_|@uN;l1+hYF}MRePDxf!_KuQjk(vIo5Y*G+-Pr3 zYB9M<x;fq2)E3vScZ+aq{&s7}iB69@nq8b-w7Zk{TDvRn=k~-t2zcoE$hFs_FStME zaoRw|lisJFhxmtWpOue%eQx^V+L*+6!=&ca{B-L~&TP!Pw7J0rg~f?aw?8j^tNkgu zxk{%4{QZ3Z060~EQgr~ABLMd50NF1ABxwMgo&eUY0CpOHIwS=sekni>uKe%+5CcD` zg%5}@;)awVFHr=PF)9Vsf%=A)M*E^KpvN#Q7(>i4Oef|$mV))iR$*sxd^ks3F>V~s zj<?3=<3|}d7#tZ&8KxNp7=0KU7{4=VFdb(aWaeb{U~XXk&0@xKo@Iqqhc%b=EkTKp zP58j3!&bt!Ml>Z>vt!tO*t<!Bq;%3EhZ#pJS(to^Oyl(Ae9EQG)x<5pozH{eN#LRJ zhVp*g<+1BMpF7_SzbpU5Zs*-^1Uv-h1%m|Fg^md`3tteC7P&2IEc!+)R-9S9T0&Q1 zLNZi}DAl^hX3r1lOETs%YqA$9R&pTMDDR`dr!cNqpkzs9rao0JR&i1lR{g5ht)8Rd zrb*G<)|%43rE^v{V($Sxd3|2}p9b#@2lw?EH5=FNFEq(Cy=?Zv{Ff#5fR|Oib=Scy zTQ$2-`)dxfPU6l!E^V%xZhPG`JzjdrcqjRc`bqhp3|I_0aJV6u9Fh^b6z&=EI?6n{ zFV;A2DBdGsE-5aVoYI_Xk@hj8=%m7_xvau$yBtjJV190)b&+K8=hCNTcP`w%*mLR0 z<=*PG+T}W~28BkOru61(Hy7G@Zu@rDb?tP!_FQ<x)93$q=Bd-r=!ntt*)h9`^;bDF z{Idh^-RCzyRxWF-41aN4dqfld;k^<0+xXABE!}O^9T7T_P6q%v2!gBd0TD$!kxFD1 zwHxJ*Do4FR3!>f7m(X(<5sWXU4zr3?#KvOp;!rqKTt03RFNhDoH{&-M%ovIp-Z9ED z9%CG2+Qk&Y)Wu9-_F?X1A+iLp+-K!sjbI%jND|Ts@7VO%YKd545OI{9%3e-lAjOd8 zIczx|kyXfboI;#MTufY<+`yf{y}=X1v%#Cl`)5}&AD%CppTu9Xn{Ri$fTBQ`;C{hz zA%CIY!g(U1BJH9kq6=cF;(X#A5)Kk;lDSe;saJdArG=%R$t1`M$-bbRlv9!WC|{xA zq{yK-qI8C8u1rv#QYljnP*YLIs*h{bYQ}1rYD?+hbw29$?XA(v(T_54GBn<&X{2l{ zwqMYM)s)?gVs2sKXPI-L$!f^@iw(D}sa=wNlf$GF+1c17#r2*W%H7hV$aB(L&?nLN zxj$DxWZ=ug3P*B-e}_7U^+%{jmPeCfvf_}VN8{HL6Ow4j$;a`jdFkYgl9QBEkFxAf z&!0)j70r8C;8sX0DlFMsx>S~UL9TqE;!LI1Wm<K4t;bbT-Ec!jqd^mLqq`-&)w+Gx zt%Z)ZJ1KXqy2b9(AIv^#>C1h5^ojYP#xVag`smjeZ^uR^?oHKCSIrd77Q8E%%lUBP z<LRXnpR!i-zLc&tthapc|2h8a-6m?AnN9})3ur*_h%eHO;zPxtUZCaB+2|#V5vB%< z#rk0TapJhsxHY^3{vLxkLoUN6V<6)clQB~VGljW^g^%SdD=TXzftgUiMr1oj6d^XT zYq5`!{5TMf60#b3iZh0b#C40?k$a2h3a>5i&aNgtFMbk#&+b?O8G-kL=Y<@Fd4y*~ z%0!*TB*a$5+ayvX^`(eXb9=5yr^z_WN>b32S-EEUJcUrj14?pK0(DJ!Mx|S|QLRk< zghqtsAuSVaJsmY&%3cvY9(^7ICPSuu%tk!M-1~)0_)KNYEX_kLaxFU#ytRgdGBysj zXYC%_(;elU!kz0~mJi9hCAvTJ<n;3NZuRByJM7;RC>E4+crn;9q%TY^{7NJ#Dk}yX zn-;edpO}bA%1CBUsZ5ngYtPU+@%+@0EaddrGnAaMyto3+!loj#;y<PJW&Rgr%4roN zm337`HQ`rm>kJx{8@ZZ@H<nx8w@$UU-LC9B-Q{sl{XV&8=HZ>*n11R2JZTwpAI3kc z9F==M^wNE7YQl2z{wvD#x!1HeduKD>4!<YNnaxLjC|T(IIJvmAw7$IjX?A6F^}*++ zFGXJ?*7kkFe|x%~MKh)S`d<CR^2h4W><!6{_Fwycz5H$Ud+?9RpSzo)n^~J*w;Z<W zwl=o+Z<lN@?5OV4(Ra~v=yZU;ehB~om<+>$!y<(&4EOyPbNBy6NN~*m^gsZB#V^#} z763>9zz>EH2Eh;pkst&XU<msF{(3b402~pZ+#3L($Bq7f>PGt=jRpWP3=5Bs3<?O0 z7Saz75B3u>3=0X5iS~<>6*3L=k&_itQczF<0QeU?a{Ut552?Wb00A^fL_t(Ijir*Y z&B8Dcg+CBIL$Cl#WC98{fSHnxE*nr}fJm%>LV-j<gJdMYiF`R4>_4%Q{(@Kf-g|fN zeDEVC{}IZv)cDO9!*;u!0L=Vjp67w+iyrIsdSp-(1x?fFVzHRaym+(O0MK<EfW{`z z=M!r!*4lA|Sp0guxZm&W_j~5^d6?jKyJ4-RZCk3U3bm<s8j2r}2LP+pDs=K<Kf!Xj z#2E9pgQ3`y`o6~)Ls1m*9s`2GP%I(<hzMDh1@dP<;~n@V#)UY)a}MVmB7$>{s;bg) zVxzJwRaNzRz3Sz1(ewGNr_)J~$D`(Xu1(YEG_^@F>?|T7A`E`PcmEd0iw}oGVvrot z2ctvsoxNFN0MNEAUDx59OCk(KB0}HysljfyON8HUpswqL_Y>Y%<OUdK%;53>0000< KMNUMnLSTY_%g(I; diff --git a/library/simplepie/demo/for_the_demo/favicons/magnolia.png b/library/simplepie/demo/for_the_demo/favicons/magnolia.png deleted file mode 100644 index da519f5ab92d84c56d4e55f0c74f17d828391960..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4574 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 IM6N<$f?ZhTO8@`> diff --git a/library/simplepie/demo/for_the_demo/favicons/myweb2.png b/library/simplepie/demo/for_the_demo/favicons/myweb2.png deleted file mode 100644 index 2a12968d5ea10ae7b39e47855deb2040e35a5881..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4010 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 QS^xk507*qoM6N<$g7XK!pa1{> diff --git a/library/simplepie/demo/for_the_demo/favicons/newsvine.png b/library/simplepie/demo/for_the_demo/favicons/newsvine.png deleted file mode 100644 index 5cdbb31c69adf4c0a52bcb93193c7a1f9566c76c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3804 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 S>-qWs0000<MNUMnLSTYECtpnf diff --git a/library/simplepie/demo/for_the_demo/favicons/reddit.png b/library/simplepie/demo/for_the_demo/favicons/reddit.png deleted file mode 100644 index 65c38867cd74b61c1720017d13c95264b75d3500..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4239 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 diff --git a/library/simplepie/demo/for_the_demo/favicons/segnalo.png b/library/simplepie/demo/for_the_demo/favicons/segnalo.png deleted file mode 100644 index 748149b3713b90ee4c3e8e8e85f73d743d0eec38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4116 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 Skr1f>0000<MNUMnLSTX@jQ1u0 diff --git a/library/simplepie/demo/for_the_demo/favicons/simpy.png b/library/simplepie/demo/for_the_demo/favicons/simpy.png deleted file mode 100644 index 30b23c1a558369dd1feeaad81c2dca58b2af6bcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4256 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 CDMdU0 diff --git a/library/simplepie/demo/for_the_demo/favicons/spurl.png b/library/simplepie/demo/for_the_demo/favicons/spurl.png deleted file mode 100644 index f5be3963dc0f2ca78e875cfccc5d021af76ba8d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3970 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 diff --git a/library/simplepie/demo/for_the_demo/favicons/technorati.png b/library/simplepie/demo/for_the_demo/favicons/technorati.png deleted file mode 100644 index 0f19e824e614e7d56638174c33690d77606df22c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4087 zcmV<T4+!vyP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000gvX+uL$Nkc;* zP;zf(X>4Tx0C=30S9dto58wWL&oqzYI96n@aI9mmjL3}ak-f{F8Ie(u%#4&IqmV=> zQc@v>sH`Lv2~o+ZRK|J!=y~7YAMf>C&-LDa-JkoqKJ%Z?bpx>Rc!h@tqW~ZzG&<5| zzk!gGvx^XZ5^%r?XdnUA%O@&a-`d&&{v&=)00IE;lHwH}9_;qs17BC{+MnT6iqfks z)%eeO002OUbaHk9Kx6@c9PpRx0RY+iFSi2#a$IzHGyoC^0A!y)FJAy82>@gx?Q9GI zkU9V$1pMV406_5m%Lf2}5bF~V4S-Am;7FmqL7@QnRREl>uV0i806`W2e0`&QLI4Q) z03a6<66Om)=m7vJpYX_N0KyUgP@J4yg#M0vhzUS-4**X7A1zt}ptTY}(D@%t;wk`_ z4Z!t_|7bKDKmY*Y8j13!Dggih!eRh`ou$)h5&-x@fb9Z0eY1*A->wEg&j9oU`@}@X z{`+1CG7RwF;=eop8)yUo1pr)N0T&T0Vu@6t(5Oh%Dmog2$24GFaNM}J_zs5ij5$ns z%;#B}Sw{&QL>2Zp(ja*^X9(9Qk3MfBA0K~_0A4U(m?TmxMij4-q)4?%E6EH{tmI}C z4l6EFgOpcPW7W|b37RzRNS*I{<Mfsd!VEtcIT{a|$e31`<1FGVC#-a>Yi$^8{p=n% zNI1qjO}ePMRvg-JckpQQV)gd+Y4Jn+odd21(hln#$qAkcl@1FFzZtO^r4bz&b369y zQHgl}gtElJq^)F1iu>{0)Rwe28R!$@CrwX<W)^4NJw2KIEr*ax$urOQDo8Ipf3~IQ zZt+;j(mA9ITlVez*oAxL=P#yK9KPgGsd1UF3aMJI9<AxFy>un(YG9ptJ+*=V8v5FD z<CE*nO~p6jn_XM9Z}Q*VZk=m;*j{lf{kCU^RwqyA?>iG+&3Cizxp%AHC*J?i)BYgq zq1z*+UR>{Z-}U~a$L0gP1M^RAJxv)j8{!_CAFh8EGom|+jP^WFe4+ee<z>y7*EsL^ zz(m%h-sJjJ{VUID&gsXmkIg8~%)Kd{-9P)|?bUbo?~(V{=RD`x=i5K{EpRT}`{=hw zSnOExS!P*o{^Y!ZU8!HST%~<3|6=fE;cM=i+S=Q1r`F}yU(-%~SN*>Dqw=TQhTz8X zuioDsf1YiL?y%D70Kg7fkO(7)G;#`ANBN=V(P0=orWR|BW5V^}^B8;?^_Ya2xmXxk zQ3NuZAW@OsffUbii9E=;!>z&-#oNA%&TqN9Qea(3Q}~?7Z?Oa7Es`u!NB2y~P-P1! zG<gSwE+t{=DdkV9dTKT5+nSzQZ8|tz$Guneei@h;=I@&~mfRm_(rd<MZf8+uxnLz> z9dYokEt{RKeYL}yla_O;OYb2DH#_$VkJnxT-d;YnzH9#S0Y?IF2CW<s4Gst?3w;*O z6rmd#8dVWJ5<`!ZJ$f)cHK8i8FKH>6B}M7DQ))!o>Gax+hbP{gT+d|95<9J&t$W5g z$2~VLFF8M~z@yOatU(dASgJ&zlvqkX_o-~+eE)^6@|ugK6(=r5R=Qr+uad6jszKMR z)DB&_d9|i4v;J^{)isqyp2k1d-!(nBQQ4f?5_Hp~RjQ4#ZMA*)*45kD9f6%jcSO4| zU5j^z?p^6lyYJOw_(1f*&cmrk?Y+f)@%=83)dx5RXisLI-Wtps3LCb5CO5(|@^y6J zdHsvbmqBBu<5Ck06U&nWQ#G$rr#)Zm&hXD{znPisd|Ui3;=R?J{5;G2hYwE{sy`lI z^jOkf=3b_K8d<r%dggP`7vryDU+HVp-)^s$(4xLu|B(O5`t#Gq<6oD5C;xHXRNW$P zecvA1X{3A8=>Wh4Vqgx(pbJ4G#z-NufYL|RpxMxA=q*etmK9r$qvFQ#2@K*4lZ<&x zmdxzTGc5J22?T341tK%?C;K$%0Y@YG0%tB)I(IxzC~v?nPd*QR*WC^R2L%rb*$CT- z*o#_<8Ht-q7)k0$Y3@;#7LgH<Wu@RL8*+>CvkKFSFO-I;1IqVR+ElNpRjZe2<Y}JK zI<1|dld7A%H$g8-Kght#(0iY!k&Us{esdFJQ!6twb3+R=OM?U2RtDBu2i0tJZ8h!G z>{T389MzmOoT)BquGB*cZVK*79%`OCUYg!&KI*<&etZ431B?QVgNzUB9MKEb4KWEd z3ZsS#MGzylBA243qlaSd#9ocdJ{lYEnP8KsktBYMc<fj5Y|4Y<)v38@ap{LL)KBoA z#GG6_)t_0Lm2)~Md;b}!99+&q?&G|g{L}*XLhZABXMYuq6}Oh;lm?vJS0-{EeSZEz zclo)CF%|ZgsFj4ukCz`-RaU3h_|@uN;l1+hYF}MRePDxf!_KuQjk(vIo5Y*G+-Pr3 zYB9M<x;fq2)E3vScZ+aq{&s7}iB69@nq8b-w7Zk{TDvRn=k~-t2zcoE$hFs_FStME zaoRw|lisJFhxmtWpOue%eQx^V+L*+6!=&ca{B-L~&TP!Pw7J0rg~f?aw?8j^tNkgu zxk{%4{QZ3Z060~EQgr~ABLMd50NF1ABxwMgo&eUY0CpOHIwS=sekni>uKe%+5CcD` zg%5}@;)awVFHr=PF)9Vsf%=A)M*E^KpvN#Q7(>i4Oef|$mV))iR$*sxd^ks3F>V~s zj<?3=<3|}d7#tZ&8KxNp7=0KU7{4=VFdb(aWaeb{U~XXk&0@xKo@Iqqhc%b=EkTKp zP58j3!&bt!Ml>Z>vt!tO*t<!Bq;%3EhZ#pJS(to^Oyl(Ae9EQG)x<5pozH{eN#LRJ zhVp*g<+1BMpF7_SzbpU5Zs*-^1Uv-h1%m|Fg^md`3tteC7P&2IEc!+)R-9S9T0&Q1 zLNZi}DAl^hX3r1lOETs%YqA$9R&pTMDDR`dr!cNqpkzs9rao0JR&i1lR{g5ht)8Rd zrb*G<)|%43rE^v{V($Sxd3|2}p9b#@2lw?EH5=FNFEq(Cy=?Zv{Ff#5fR|Oib=Scy zTQ$2-`)dxfPU6l!E^V%xZhPG`JzjdrcqjRc`bqhp3|I_0aJV6u9Fh^b6z&=EI?6n{ zFV;A2DBdGsE-5aVoYI_Xk@hj8=%m7_xvau$yBtjJV190)b&+K8=hCNTcP`w%*mLR0 z<=*PG+T}W~28BkOru61(Hy7G@Zu@rDb?tP!_FQ<x)93$q=Bd-r=!ntt*)h9`^;bDF z{Idh^-RCzyRxWF-41aN4dqfld;k^<0+xXABE!}O^9T7T_P6q%v2!gBd0TD$!kxFD1 zwHxJ*Do4FR3!>f7m(X(<5sWXU4zr3?#KvOp;!rqKTt03RFNhDoH{&-M%ovIp-Z9ED z9%CG2+Qk&Y)Wu9-_F?X1A+iLp+-K!sjbI%jND|Ts@7VO%YKd545OI{9%3e-lAjOd8 zIczx|kyXfboI;#MTufY<+`yf{y}=X1v%#Cl`)5}&AD%CppTu9Xn{Ri$fTBQ`;C{hz zA%CIY!g(U1BJH9kq6=cF;(X#A5)Kk;lDSe;saJdArG=%R$t1`M$-bbRlv9!WC|{xA zq{yK-qI8C8u1rv#QYljnP*YLIs*h{bYQ}1rYD?+hbw29$?XA(v(T_54GBn<&X{2l{ zwqMYM)s)?gVs2sKXPI-L$!f^@iw(D}sa=wNlf$GF+1c17#r2*W%H7hV$aB(L&?nLN zxj$DxWZ=ug3P*B-e}_7U^+%{jmPeCfvf_}VN8{HL6Ow4j$;a`jdFkYgl9QBEkFxAf z&!0)j70r8C;8sX0DlFMsx>S~UL9TqE;!LI1Wm<K4t;bbT-Ec!jqd^mLqq`-&)w+Gx zt%Z)ZJ1KXqy2b9(AIv^#>C1h5^ojYP#xVag`smjeZ^uR^?oHKCSIrd77Q8E%%lUBP z<LRXnpR!i-zLc&tthapc|2h8a-6m?AnN9})3ur*_h%eHO;zPxtUZCaB+2|#V5vB%< z#rk0TapJhsxHY^3{vLxkLoUN6V<6)clQB~VGljW^g^%SdD=TXzftgUiMr1oj6d^XT zYq5`!{5TMf60#b3iZh0b#C40?k$a2h3a>5i&aNgtFMbk#&+b?O8G-kL=Y<@Fd4y*~ z%0!*TB*a$5+ayvX^`(eXb9=5yr^z_WN>b32S-EEUJcUrj14?pK0(DJ!Mx|S|QLRk< zghqtsAuSVaJsmY&%3cvY9(^7ICPSuu%tk!M-1~)0_)KNYEX_kLaxFU#ytRgdGBysj zXYC%_(;elU!kz0~mJi9hCAvTJ<n;3NZuRByJM7;RC>E4+crn;9q%TY^{7NJ#Dk}yX zn-;edpO}bA%1CBUsZ5ngYtPU+@%+@0EaddrGnAaMyto3+!loj#;y<PJW&Rgr%4roN zm337`HQ`rm>kJx{8@ZZ@H<nx8w@$UU-LC9B-Q{sl{XV&8=HZ>*n11R2JZTwpAI3kc z9F==M^wNE7YQl2z{wvD#x!1HeduKD>4!<YNnaxLjC|T(IIJvmAw7$IjX?A6F^}*++ zFGXJ?*7kkFe|x%~MKh)S`d<CR^2h4W><!6{_Fwycz5H$Ud+?9RpSzo)n^~J*w;Z<W zwl=o+Z<lN@?5OV4(Ra~v=yZU;ehB~om<+>$!y<(&4EOyPbNBy6NN~*m^gsZB#V^#} z763>9zz>EH2Eh;pkst&XU<msF{(3b402~pZ+#3L($Bq7f>PGt=jRpWP3=5Bs3<?O0 z7Saz75B3u>3=0X5iS~<>6*3L=k&_itQczF<0QeU?a{Ut552?Wb00D<dL_t(Ijir-M zD1%`f$3O4vovBA}{=}NhTqb|6J1j{`$N?z_r8qfoa&&Q6ZjR291IZlhz=5>G=uL96 z6iJe;4Nc=M?48X)-qEJ{^F984zwhVyJ>Q-O{wWEddsE41dIE=VvCw`*iCGu}+t<qk z!anYkHvp7bs_1i$<EidNk|fF8x<N+M699}g%oygUl*W|WMJTT3oEnV!X|wh7noea* zA4MT6bl8WCA@9*hUh`+j;;9z{;4E!IHdz3;dyWu{`U~EFH@un!{6+DXSQc>kc!Hiz z0#IqS(^Bp!2<WLD%>pz%K_KEYnzQb89K!Xjxux90Wb+(S>js&RqC>b?>NqyCcuHxk zoX>H1yF)0hB4h<;X%n3_!wl9>V3LZy0sz`<{dnDL2BeU3LJUUzTs(w+WL9HanDaF% ztTq7jbdo^CN1bT=(%*~v*$uA}e~IM+EJao-t#;Jd7W1JguC%jnHxROt2Vg2@`e5Kk p4++Q(@i!8(^8OqD3fUh(dIMdxfN`P@N-6*V002ovPDHLkV1n~J@8kdg diff --git a/library/simplepie/demo/for_the_demo/favicons/wists.png b/library/simplepie/demo/for_the_demo/favicons/wists.png deleted file mode 100644 index 2e2d294d1ec6198f50c3a37253cc7252a2d4b3a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3974 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 diff --git a/library/simplepie/demo/for_the_demo/feed.png b/library/simplepie/demo/for_the_demo/feed.png deleted file mode 100644 index e23c50c85b2fc311da507ea1a36b05c859477153..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 715 zcmV;+0yO=JP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt007q5 z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!T}ebiRCwBq zlg(=rK^VrL*-cstQMY1@M6@fdv=u*E$e|!;3tqfvi+B(b+rOaZFQ_7(wKs1q^dN}P z6a;V0NrX~tkXQ>+60z1wux?1B>3(E=W@nR>oP6QkosV~Z^E|V&0*rblG7hKyj7<|R zpB~c<)A3vFRu=s_()x^_5#I9fB+Bb^NQ^=o902q=7S!4nA9z>D|LDWt0tlH9&YLs8 z#jznnPdxz&cY(cirJ#}Zwze*Sn1Hh);QbKANWE4yq65Rg<~`uZQ=r>ml?9ZRfL|HF zw%TA+@fV0t?EaQ!YM$2tffqnI4@9{*KFS;i&eO_B0Z2Y11S<E#-18x#bm(aa0KqN} zCyHs{^AxZ>&%ULAgFM@jEGG>GaYC5e0Jxn25xX4php5d2R%e0NXMy~41aX|Iu4V)2 zsD%J7aa;m+-|uio;=Xh6!~@{m3~+M|xV8x72wI*}kZ!^+K2jEJHDtRwG@BGuLumyb zTq&n!lic+VK|k4Fd921#gm;;$HUMZ6paXxx3$jMZ3;ZIl#<&yDpp(9@^Dy6W=Soqn zx-_k78w#Y4CV-a-x3lW-UXH1%IHmVb0v~2ozjwR|2+qKltp?x<CH4?9LI2y7{%KxS z_T2)$WK{d8*#yY7S|Fv&moJoG89O;uw6Uc6(n>kBAmhRf%I^u9V)cH!U>vs1-eg7H z^~7Zbka36$PtrooQe!67#W>#Iht0QNVdZm037{z~8ZjHE6IFC{@xDgT%IJZVch1r9 xZQ~TIqV&z5Nj^h}`kntTq@Q$Zp_KV2zyNZ!0q^m>#MuA<002ovPDHLkV1i)rMIQhF diff --git a/library/simplepie/demo/for_the_demo/logo_simplepie_demo.png b/library/simplepie/demo/for_the_demo/logo_simplepie_demo.png deleted file mode 100644 index eda2d868b54949e2f4f7c12d21f0ae1f9052f0c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3047 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 diff --git a/library/simplepie/demo/for_the_demo/lucida-grande-bold.swf b/library/simplepie/demo/for_the_demo/lucida-grande-bold.swf deleted file mode 100644 index 0a41e15eb87253083c1233fbd9e4aff27d438082..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21159 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	JLhMM>=Kb?J~S+n_*SrvEA{}eAOAF^Bv~YSUSuZ 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 K$o~KoWRaGA5MBiU diff --git a/library/simplepie/demo/for_the_demo/mediaplayer.swf b/library/simplepie/demo/for_the_demo/mediaplayer.swf deleted file mode 100644 index bf78fd919a3587c75fd5b2df7ef463a22636aa62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32008 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 diff --git a/library/simplepie/demo/for_the_demo/mediaplayer_readme.htm b/library/simplepie/demo/for_the_demo/mediaplayer_readme.htm deleted file mode 100644 index 56e12c309e..0000000000 --- a/library/simplepie/demo/for_the_demo/mediaplayer_readme.htm +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<head> -<meta http-equiv="refresh" content="0;url=http://www.jeroenwijering.com/extras/readme.html"> -</head> -</html> \ No newline at end of file diff --git a/library/simplepie/demo/for_the_demo/mini_podcast.png b/library/simplepie/demo/for_the_demo/mini_podcast.png deleted file mode 100644 index fd6faf2a318701ec923687afd472e5db89ffd651..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1202 zcmV;j1Wo&iP)<h;3K|Lk000e1NJLTq002Y)000vR1^@s6DVVGM00004b3#c}2nYxW zd<bNS00009a7bBm000XT000XT0n*)m`~Uy|A9O`nbW?9;ba!ELWdKcKV{&h8Wn^h# zAVz6&Wp{6KYjYqtwP@0K0000MbVXQnLvm$dbZKvHAXI5>WdJZSFEBGNFg8Pc<_`b> z1P4h(K~z|U<(5%s8+9DVKbKs3UDGUUdPA;ewlJ|+hry<kiq0)#!o+Pl&66)HB1E6; zgY>}%3F6bRK24+ral#N8EuCdZMQ9>}wTxzKK)a>7uEi{AZ5mtCw9DG&d`NQ1<ucjR zY~vq}knivN`~H8w@BQxXFQ}@jUSGY%$I&mjem_EdBSGm>EOJ$1N6qbgWIx06^0Qd2 zR!k-n0758rQ$2d~J?g~TZBgi>#Fp1rIG6o^*PnichK2@ACKKk^+oC^_T_&DMfQoi0 z7P;!NO(9EU@is4{Tajg%nwlEK(Aq8H_siU?k%_PWKd^|rKzK4tb8|EG_4SDVmT+W` zn<IPN)SK(Lu<!%%t;7R`cQO`@MoFbo0GQZ<!ljt=30rXfDL<E<=_R|R^6AW1oNx6L zboCo}m2!6)mdRw;+}s2}WGnBKRCKSa=_F^A6TI{5Cwv_n2H=g!54rQwFp5~gWvMza z$Qz<aGV!&NU^e$NG#{W~SWAt<#rdD`HNA=d_kNA49-q(&dan71r00mQC3G?GjUEOU zMl13QdzZ#M*L(z%p$FoMBoljiM5pDw(Zj%VXAMo3?xC5nW;6ppR77L&fOS9LL@yIO z*oXhOekNC@`M&*gc8gZRe^29&^lQQZo^c<+rF`+PP2d^#5%Y{_xFX5Kj^+Lr_>t8) z;+cfT4|Mk74fQZMKdRFSboLqay`dfk+-G&TBIUjuLaal_E9gCp831B=P^pzUH*=B6 z!{1`J)HCG%jNaQ9*ezNCm|ctKp1<0naG<lVcsH*42qtnxlFfhO3VY-yuTivDoXDlj ziZ+qLa0g1QY|t<8gy9a<Qzesg)n{n5RIkumrG9mW7!uOCgvpg@4$JKTJXyC-<6OEM z%r%)V3VW{P@Ay6aW{qaMj#t=gX<c7vquE}J=W+G)1fDBCO+T0jq14L6ULDbR!cfk% z_`QV2FYwCsN_(r+ufdS=;A}dk84~<6L`$6lz>o1UW8<RTAl!8MII$DOXWa9%k4S0` zfWV>R9rup)FnD(q;g1ef?}UyYTr8WkbaB$u(`E0XJYUyGmf|!&gc$$=-?>#~m+_6; zy1Rw<_rC>z|CfH2H&RsMRRh*Neu%EFE?h1bqSxVJU@>4wi2OB6_e8H=WVM3u4#)P+ zS&$?NQ4|3%z3V*5t_@4^uu_vxC_TVbDJHIidq%<ObYipFa)!LPubnR*@1}L-v29c6 zqrmpwv2bkc2@Wa;aJgJK91Z}4Y&NSVlS!tgrU-|_M59qMnGDswBuT>QbmDTkXlrZ3 z;cy@b0)m=bq|<4V$t0;%N;}tA^Abf7o6Uy9;lOIOA_xLN&XE7Owr#ck0iew8UF$dq Q>i_@%07*qoM6N<$f^gwQSO5S3 diff --git a/library/simplepie/demo/for_the_demo/place_audio.png b/library/simplepie/demo/for_the_demo/place_audio.png deleted file mode 100644 index 560ea0039310545ed1c4dcf7a1004abcd2b780af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 851 zcmV-Z1FZasP)<h;3K|Lk000e1NJLTq004pj000mO0ssI2RnIWu00003b3#c}2nYz< z;ZNWI000SaNLh0L02}=P02}=Qm(d3{0000KbVXQnLvm$dbZKvHAXI5>WdJlUH7_tW zlXA8~0000VbVXQnQ*UN;cVTj608L?Ia&K*AWNBd_Mrm?ocW-iQb09diXwrB900OH? zL_t(oh3%KWPZMDn$3J(}f=ErHU?>n%8C+ZlR0kcn$-zjh0|O3XOe`yde?b2L`wuXX zhJ_d%8euf16FM9Ts~z;>k5g1Ea?lD?UWfMDYwzt3VrUcF@AT&TJkR%i-uL_T?y4|9 z%G1wi{dCZL<OCbP2oEaUILF;VE}m|6N9~ID8J>Sp@v0pUT8T@&^mcg|>5)dYfeG!x z#P-2%!b}=_W|MnsFAVpneERL?<5#2cYtiJl^|-N;xc(%1CDQ!f+6W#+<*b)9?0^Ls z$ns!hDm<)dujZ29*S8O?@Y-W-Ak^wk{|g*J4h|p-8L(gn(vZbd$oK<54uE}(9n&yP z0EJ**An`64?$;6@lc>-IAZGM@o${>UkeJqsYd0gNYvsFZoh21j)fgvPSLaw#?##L* zpe%(Dh}jYYC=2p|_PD8_FAz_yCT_;&U&nyx*xcCp%kyh7?5CHA=tVgZ(UnD8I%`WR zWrDSDT<U@Qgf(<(+bK<GT&fh!A!c-CIfOvm)0HKge5fmnA#!EcQrdbU1oDs*5*y;F z)wmX&42%Qu)Cyo&roY=KtuIq|zh%;ng$JPEnNm0^`Kdu-UMZo9Nfl`|GD``|j)WWt z`vH)($z2fk+OnheDV&ED_Z?%ND?~*^cV{*Ni4BHT_%8By`+~k9VEbS<VI-yXb$zbr z)tO&+QY3s@vM&2+%B1A_mSbHUpu@_Bop3jwC*2zz<5~cz4S->plYw!+w=Zex;h?s$ z^|t5@)orgf3$@22A8n-Re;Kz1rQBrWn@as?v>$E<m>Q~J{a#-*6xsNZ%0HAkI9a^h zRJb$5tw5_AX>YtRRW|oq-9_8uLq^5e<E!M4bO%jB%Hou&@MskMCxbnlk-x4xI6>?m d@n(zJ;9uZFSmaT$)Vu%y002ovPDHLkV1g28gKhu- diff --git a/library/simplepie/demo/for_the_demo/place_video.png b/library/simplepie/demo/for_the_demo/place_video.png deleted file mode 100644 index be5ec8219eb952d55d581e40b104d8a74a58af2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36713 zcmeEu^;cV6uy$|?q0knW0tJdgaCZvDwMg&+!M#9nN+A?4?oM!b*P_9SOOY0Lm!MzX z^{x94+@EjOIxATx=bXvxJ@d>nvu7tvT~!_rha3k00N^Qnl+^?PP}Y&hS8P<|8MN;m zhyT5~Nh@e$V`DG>RYQIUc;Wm}-wgnuc>M1`N&bz+k332A<%8ZA8COfQFE&n&G}<-} zmH<9ZZV)Gkf8p}J8F>Nqe-~&uLB4pJxmp5L%*<V#94swt%xL6nTrEAFT<zRw1RJcU zz5)P}00mho?XMY!E1n6Py6&8Je&dKOlm5!maWSC@rk9_wr9YwuqrW2sqH>xaH&C0p z9ocP=_;%P=?HrsQ_rY&nqa27$(I!Y~38dbU(sN^@f0UL^`%SNsl{HQja22tW@%VJ> z-XvEmtC=!o9WYm}Q;}7g#WFkEq;^s_@x%oHU<MONW8$KsVV(}J?A(PBNMm~yP#pi= zxnutCqdnndFadWCQ_c%o3rP*hH_}6ad4cbPf1-RqQNgA~3r9^u-c5+XOB)Pi!m*ad zCR6{v8~$&U{#T|kA~>%W_|po|2EP?sIqclNHnneTn|z8tEAJh@ihs8wfwZr;x>@jZ zyE=-o;RZVaRU>DH9Yf};TnY)?6GP9+Y$~B=q=}wehYYp$u3Q;VRH@<fNz|S8|J&k> z+z^c3NKGZ9m2ELx6&H9=r?7dYl4T1d?Pm1MJ}8nJYS{_h{uC17e-cDt+`9J~5QUvV z;gaEZWVhyj;`6MbSkgc&t<6F-=px>6t8x`x;`W#@>~#e7vBprzI(nN32LL4jf*Cxr zmiiB^2bHTGKJ%dx4>c13PyBtLAR3U0+fhyLa8lBzB?zM$Em$hvszfw7z;nK;)BeH| zfb&YQ3BU$;8Tf--gN{WO)-VI6E$s`9X+QCMu60^NS-aT~?3@-a2=Eze@u%8+_`qs? zZg1`mIJVsoAX&W`Jn`>?n{0WBzuRc<P5}gprd`B%G1;*<Bq=IX%%<_hpZMQouX?Vn z-gMr4SBN7#R=rb!L4OJPD{MY61fsN?Js3*hNoRp|uzpcFJaHB^t{OE5JNW*_m=O80 zyQ|xEmsw%Z#77<v00IbH+>bJf>NHd5G)L^WlE-Vyb;^uAS7v2oX6fWrGlYC`ILa~( zNaV-0va@nuV+my=a`lBGGYw)Y`UjR8>;?x1^NDQIAo@v`i`!%A&kqyN7Dr1BKM=h_ z_|_!}+ALa%EDUvQGBcWrumnwEI5)F<+3eF{a_9MtNEhx~_Pk-Ob*K+h&al@I{uum- zD!}<Hy>ju~5a2g&bn^)B`qSLpOgMOE%fSjGc=ZYe@T#ig8g}BlBLam&>va&E%&>$K zO>Vd*EFpngMT_|pWzmXb*UIs#&$C~48{$HK?Q@|)hTCKKFyaSlT87%gq;GD+qfv6Z z(1fL|>8R-_CV}EG_0#Ei7b3kYJu|b92$8SDLk{qNoOimNHSu%EU1w#0jn=Y_ai?0^ zB*Dy5Hd}nx&~5^*w+)JlCg>G3FGa5g(_1N0<oq~HgoHRXB;zk6>Dc~&T(_KzPtzg> z_(fTgQO*gCqc{8|ni1P-&(jiqrxJxc=4{V;EJSOUh4V&UTU$`tIQp=>;^`OQieo=~ z(bL6r2>~&`_X?WuTAUE+T=in&h`InS{x_Tua8}*C+PE&^<Yp>7GjtxW<20b_p8yB> zG63N9%x!)>f?d6Jhyjr8s73kl<L*~jJoVjVXxDXvQ{}*=t?&*TJS~MZ00B4AteQVw zzK>6rIBVJ4+oM;Y8e}HKnYEpD$q<Y-sL-j0-aI*r&MvkfOeDvhkkZHovt|#Ml?FN< z$F}%LjKB66tlbT;R<*zGHXPd$P1Akj1+!A+9IVqp9NnLEJv%*LpQvRBx_fZwmd(1h zME;PEre5i~>3N8a-q2=A%*~};yXgbjH-N{Q-yGG~MTj-Dp?_IT7yW=V1)dO|(8VL3 z)lPdi%dvqd3ct09bgnph{U*mFlZXCnKJq>*qGamMnyJJZ#R^|-{r65CS4vHP&KdeZ zWwZPLnF^rWeMJ$5R57CeGbuD8BBDZvwRuJ-nYd2Lcf|5MAwTdvTIK4E6KRX<hUV49 zvn%}cbjX%t&?SRDOp^s(>-)iX#hue{zU`^|=CM7erq$M1!J=Zu)(1L5O**JRa7jX9 zfD{;#?(`Iy@ETw^U!hYLC6}+xB&$*1+`M4V?_==QBdp3Jc{0ClWphs&MFmrf?h+|( zU!bw$^G(_qAP`$0YJwP<mpJ`~Kj^{+8<i)?TwzRJyX{!JKbmjfPE4|_pm%*Tfi;X9 z926vI5Qdt~9AQpRTZvy+QA_w=>Mh_iNH&0}$jF$9BOd(&TwGjiY)ZOTlsUdIB@eeq zAtfqi!JSr~!j+2n>E4;62BM`zrh2)wtT;aQNjz+jbe?XBMmNk=*HScl=W8-|U1UBl zf6_I|ojD(lYd7bJp7xgj20iUfPd{JRP{dFRy`P?O$xv(nm+Gi2j~|dC$Mo?C;+@Ot zNy3bjCySIP)p62gb6MKt7|_oUpH7Abz%&IxP)W<u{jE-JsYJh?az6cg_S((Ow`u}q zM{)6Uaa|j9T_rgzH_|ICoycdZf<#0vY62c_Z8>y9q2y7}lY28lm24p%F%hK}WlUTQ zUZK!KefBQl`;zC26O;SiWI(rKc1D9MRhazY)t2SfZ2p1^rQa57Q^4tWYKJBfgszP2 zxR5yz5KQ1DF|fILyRa~n{QTHxE+1X`{1bkGeBt6AsSq627Dr2AzQo}5y4z|o6bH6J z1=N(4QwqJt_;fqYqMSgH58aphdAEPn#eEdzjMe346o>o!Vd;){mlaJ<_xmKA$?!zQ zeo!RRk<hSS%4%pkujC@luSA_A-ij2*`n;a`t@>Xhx2?YF{7x5J+YwOYEF_W*KC(4M z{q*<5#2*o%qdOK8329iOFG?7_=h6v>yFNdmnLPZ{wX)h#na)T}6>?seB!x<2Ph6DF z?$7*^t<c=NxyOx&aJ#rT8Y@?8QO<gW^c*b*U0#>Rp^GQDr2ysa*{Yh~KCVl~<``Uq z&{99C&?c+Ri^>b0r1HEG@UXSJtCuydSdvH#X?Q*2YMGLuJWU@y<=8}m{`>H7v~BG% zYj2Ooivu4IZ_CeBiU2)RwQM%e@co1+5)GJZc_;&BcQ2mdmQJKqygEDPABT@SZaO9I z@+HR7lXZ87#Y*uu7&u$bjZW@5q;qrJ#aG=L)Ho6~A^PMW6q+c|r386+E^1;{nggD{ zF@;kn!f|nN#~?flEX4t-qFP88Wt<;(K1}SJcwP~6$|&a5x#Iv1C=VpvJQC1`-<^0( ztoc1%JU^veY?{Ib&P<*UP7M97h>|Q7Qny+tp)LfMZ7WZQhlgE{%Z%4Y%hVrYcf1<F z6~<K(k1h5PUwRh%21dq;yDhEccKZuAmFXpq>2z<I{gQi`;W})jC1@V*#u!PQKddcZ z44f&VC1W{}M{ZTPe${!}*5c*`P>TERc0J#8iTmCTsfF!H_XON8KVN(xIokL5PP02P zKJIsDE+Brp{sb}{-|}7K#129r5RkgXHHfHti+w@)?;DS=2XpsyK60F2$3AeDr1U2i zWz3sx`rSNko=f=gw7aduKNZDW(IKrz_7)%V=%iw@W%6Y@R6@IIp5T=v2*Ukd$MyQ~ zyMGx;LT{o>;N3;`^FJ){)4v{V@oTi%EXfju>P!i6%Rv{xornlrte2G7eM2{o50s53 z_ei*KbgW#ohvF%ls83BWo2~fP8mG;obSBiiarsi~pbGb15BOV1;d%TA6TqsKn$(3= zURbAj&iQ;UMveH#2oB&wScu`Bm&=hvF?0osCLhR0WgaxCs2ZE5ZJ#_H2lxz%JY7T> zU%u<BUjMxz<^m(z_=_M#9A|6p!#F$kr<6Eb_X;O`w|SbjZ}i5tbjq$asGkL~QO8?$ zykuk=*bVlxb}*!naIkFK(2H5f$x^biF<G6p%Zf9mxGh>$Zyq&$8~S|jM;Bp?1a@Mj z+5II=*soIi<wlj|Mu#{}R8Zi9eDD^Wv@-g>c_UgR=B=2x<J@<C2vHhLHVeHz=)FW$ zotw}}JZ_(J!zWe67PG!PiTk?eMwg8Nd4}_m$-L0E6kUn!qMB)5G3WKk9f{{eNZst- zm+O<YwSy~CbFXZ}4&xiqs<jhNHn#fZBj1j450ZdALk@;<5UY`KhqIQKt&iV^oj@15 z1{zbvY^UG-)A7j@^U3RB{2x04Pj^+%7VxAf1S)kPTJ@}^@_&A~hRBqN%%fMC7U9fP z$kP3K{xleG#fl`B*~81p&^7V8I5<h#!59cAlBpU;kGOg)Nbq_(UJF>C_dk;8xP<9; zUSytF^V)i`?P#>8jVszP^Hh9FDq(}?F#&^$*J6TbM0xz~#zg{(>phG-eSLkct%ok! zK|m`jE94V-fqM}FsaLMX<?Hu-5`Kei9HcAPBkr8gQ2CHb4+GBZAg{H1agdl;$N89f z3*xR9*VA#KnqOr)5eJyj;X7`!;n{Wns8v*Wu@!K>ffb0FCQ60`By+cD$eC{8Jg2rm zbVa&h6UF>z%E1*!0fji<aVzm3yNBW}4;x2K0UOuLU1yC=k45vH*WSyG=i^%sPYCSo z|9(795EJ%YM8sISxcfy<$NjXR2bI-V3;HUjU^77piM1n-jaNy=pETPT{l^VCn#MM@ z@nb@U_bRE=5^cD|#f_O}R-dk)H==x2vAYSb6eY9$d<|{b11|Hak5=y2^P!t4-4tuL z`}AQGt=IsX+EBOv*sen2anE3y7cWTC?udHiPHpjCI_uMa+Em9_*-6A1x{XzZm@OQ6 zBqt?#cNmvwFSL8f5Cj*gs!U99ZfbF!d$~H#bx%AD{O6A`0T1zA&-Yy&Ruh)2JBm}F z$h$`-U>^)N$LM&s`RzZ2T&Rk)*^&kZ9hM#4+mJ!2!<dt7Yf)*$_GVLUV#|B2?BHKz zFH|gl*^{5&sBR09jTo(<Zr-EoJQ3jMElAjdwz~_rT`$?y>j3QC5$&(5ZLhdjPy9|K zU^cCH{TIm|e?vt0?e!=IhxNnSy~!%tzoFs374_Ynd^M1gzzysx{iImPBXm}9q!(w! zjszUi1*!^HFJ<Og+lHj1B#zFn^T>?j21%(syL_x_cB_oqwy&xJt{BD7_H@edVr*vw z%M>c-ai8|9p00y%J=*s_&Cl7&n2~aFG$K`G#25Ptih2X*#qIxW_Vrh7xnMMM+?Fy` zn<_W#!fkKT21mDk7w|d)jr5j$ub^TSN?H@tZKQx#MBT2bdDDA;JeCC%VQ~qr-rmC5 zWbCl^zfcnwN`nz#;MSV%xZ?7b*2dcX>J8GzJMT7iogPN2472I?Y{{r}f_yA9qX4JF zYW^<)f;&(%y`))dUdVN$Y2Nirr^2TGe?|)Zs`$tAUX7#Ua!N3IzRJYy{>Ws<@Nl6f z-`MG4lTpI!wC8)%%Jr2%=b1ay%=htN4UAZP{%ybfmYjV5suw@tb|4jzIxfwl^m-a( zI8pYgw9pc!FlEax(qSAxn#YP%aCfoAe!@0I?Nl%2t*tKi36{W0qx-@5?3$lN@{lX+ zUtzkq-|eZ4SMt#Qwnd4y?`)ZItmWRw65{s?iQ+H~_UiK!zSuqm(A@t1n*Cs2O->`B z$*HO`=M~q4_xR%Pd`4uXYjr<3A46(T$EM{e@OKLtp~H|>ofu2`*uSGCT1DO+$2I2i zJ{{|0PW~5AJc?*)iR0Ge82?MUK9D1lJf{mdc@K*CA53R=S|W+0*GVK7ycU^ZDNid; zUp`?`@3J+(5|V791;}SL6-edM3Nuw1vHP4!d&_VUoog0Nyey^huD3~DPhb1lbVNKl zjH$`Vul>5(%S45QIxXRibt8j-pM#d-<ubJU5Vt)#YQ(Mn7!kZ7S)gO@s`CV9g9<?O z=_dEFnu~8%H2MGC9Nw#&7J9xd)isKz-y2cdh~ye*Hgg8--sIElx(oM23Ha~do^6!P zdNISMa0ZTw4l3U5@E}cBk36aTbYn1v2g<0L@u%I(t&=)Mz&@qWh7@z>0htV)1U!ct zU)PqAnXf%vQ(bxYfg<S@-in@@y45hlHZ$5P4+NtLq)FwPmhtk7C4hbNPD@|Tmhj9E z{4L>1jq~$PRN!o^<4!-H&}%l`s0-p)dWFKGqUXbjr`^~+pjCtMCXofA>iI70L80;w zt;Eqk6Tho1h1Xlz<0SWG@SXNPdh8%1Z#=ZkK?~dvY;-tTsBvFztjKU1_}aC0awXhh z+%&!CE_|d@Mg`R%l^cx7ei#tw!U`;;1@@g9lA!eZORVmSRh*CrsUa!SU}0t1$^C9; zSlAgGJeo?x<M#BgaTN)APtSL(5CW(fHZ7^SW+|udU%1`8>f^Rk=N29H<H9P|TWv*` zab&`m49~dmb*3^u+@J5byqoX3`)5SrR9-H0Ffk0rfKxakM@u4G*j+L6gPq^iEN50m z#uT_h)D)q?n(hO(^SZk%QS<$`JzLf6y}?)Ig<;MXy^$Dqwm+UF{i~hee2X(+3^e-t z_ixc;v5AO{=Cu<O=bme<w+n6cGqw$2M2PMEqqB#jO=sp8%{8V-Zl^!govsth&s8NF zcJ+j7?(lnJHHlzArYhFle{>K9$;v2`KZR%8paFJ7Ri>T%4h#xymROPGUUH|RM?U85 zbN2HEbydsuz6yna|3&(m)Gr$Br6UhyZl+TfBK=qF1QAfx{LV?z|5rj}-3^IT*JIhu zAGedm6&i%!>Uolh7K>$MmG4`frRBF-Qg>ZX^Y;_}+E2f4I?5`&%_!x;cCtfloI2Mp z$jrN2eri$+Iv0Q|Fr})Sn^OWtYs(Fp1?2W|E1a1pYl_{9z;8Pe=H<$-qL`PLP16jW zWvd<FH+~a!iXQEI|46VRrZafpwU>9U=a(IyHrtW4*SGS0`wnjgVt=~%Umq{B43O?n zB0($TLNMB?6X7P}VefO(_I!>ERo7FR%eCbXAo$G8uZyfEEDfFkYA=kQ*{wR&xZ94C z$I#h<{aK&(^F+?<6w<M#TW5rHO|x*X33ExqQrGk3P1ke&Y*mVcfNcBqQZgwtwJ_Xm zK!_yBZ9Jc8|EhJxv1-=VQ~0RL|H@#k>!MQm9clF5r^EfUsY=#xItsihkC`e8I#}(- zJg^U>(#LBw@4{wa%Mh#%PAy!|5Q(<YC)S%w%bbvK9<)Mnwze+zd8gE<MV2P2fyAUU zv8LCn5_Q7eyN6MHzLYns_wmTuOC?FW=>)~<X)C_p?(-XInt$2P2x<zbUaBkB^BGO{ z++AJv<DcH%fBROtPVH9}P9rxr=YNbHV0z4fOt2YwiMz9hmsvYo3{HbCa&l0Ui?oA= z0mr75qib-5PSdg-e{!o-h6Mqu`1H7UDi1!1fbk`2+hlb(-aIvTM;-wtsIY$9>+c_4 zV4t&5sM`S91sp?`H~1$vhNKKOZ3?X*w~kRj?2#aN1ewxxc)qBF^#%Wba6Regd)NgI z$rsdtQ*%%Nh<|q)v3P+0aHX#9g46$OOX9X?Mc44`L5ZW~BXcTeHkm;dZ+$JL&X3k< zo30(Pfb8c~E6(hyuBWZagI_M_)qnmpE(PW$jKpBP74ayY$3^n*y;et3s2M4y)a2fm z!oq>h(-2ONpyKS$oW1>(%3l>yi5S8QbuD#LRftsJ6dFv4#8At$LbJ3&8|*&A=%<HL zjzCN+Wsn&uWy92n3)^5VubvsH^EiQ|E#Iav-PZZ;K*Y}5)+SA={k0l=(|<MreM1N+ zYTAP~pAuW?EN;%`?+o4SS6mX8HQtB!c@Z2-^)_0_XfxN;dA0uiKH$D7;6P*zv1+h- z4q@gQ_-agu5}2sEIjai1`W@qcsNrrU%SQNsEH^HJfGCo7nIzE{gWLl`0@J*E9&OYQ z>+%o?1euML&5jUSSYt20ah%P%NmAWYCk{3+^H{8yk!PIKOA77wo&Ve`Z5FH%XOXJT z5+A2eOe&4b5!{0z-wz6Fm?I~BUqb+Fa5XZ>-Oh+fXkM@G_6u2Gq$J=TgLtd!d!GHP zxksti<bb~+Er+FgY`(}1d@m8zQt|nrI^9GFg#UTDahi850$9ds&1_ks-MSg4RM~z$ zQWfxcfGqZ2IJImgg@p9)i0=GzKPaqavt(O)>nm8TGuOL+`Zv_E{Y<M2CDTc?wbG{# zuc}O&1;HptqMzi^w&Kw?z4vA52$?k7dfxlszkOcCzrii~m(aA3E{E$ldmf$`J3PZ= zsoJBOI9YC3hO$w4KpV)<KHs>1mA7u6Tje*nW45sOCGdi2j1sdK#47{%XqlwQEU0BO zB2<f=$WJB@D{vrdv{6aQ*(QJejb{=&Hh3}mi(C^Lz`Pw?(fCrXI>}pT=nb;?<WKJI z?tWNIgpTsgai#Uj!O3Si><uHt%Bt%;v1;?cdPvQH6$60e<jA_9>U900{WgBraceTn z#y=PzWkHeWeBbh;y4}^p^TY3U+l=HGc6hA=1%MMt41ebjwyii8O*=LSSEv#cG4UPz zy36(IAjmO!ixO}&cTkCT>{4ctQn-=Y3kr5%{L@4^zfK86QtJV|LeOxnX33_$T+r@| zK95Dsy0MmOLr$D~sRi>t5%IrV4uei!$e2cIWaMc2_e=v6SR5!sm;XwSUeyol_gR@? zrUp5pb_cpa@SGZrIF;h%I=F5+@}0+8-L3{=FVHY}5jJ1O*a&^+-Se4R4{PD)EN=O~ znUdjSkc-2>tgNi3W6v&wve`KL#Wv5X1L20H$t<z9czWX_xE`}9XdtO=>*ZU~*U-IR zzhuS1-Yza&;dwOJ94(js3_35PRB3j=8Y0|2C9oEg@;R11I=65JqI_<B4*XfliFq4B zEGgo@lRvHQ3i*A*M$Mp>J|ziY^Vu5_vr*t*J=K<7^qMKflu;%@P$qrl!L|1a2*_8; z6iu}ZiA--~2hxR2Glkp3zfgJ>Xn3;q)dV}FTAJ`KKZ>aGSG9ck!_)tT<TXZ8Ad_mo z_8nciNYQmtc>iT*^hZezRxB@6WS*iHpOm|XPc`_m&)L!O_#4*S^^6^{afGkM*RPc! zt4&U8w&{b%E6E>If4OAbQ1J|$f5re<S0HV*Y5C|&Yrus-K*08>a)!lgJi?cCBRV$U z`BuL&QHi-WV?(~Bi*t%(9Z-0DOCR{0)-T<*CEa1%K#9dypnoXhYu%g&EUOy(_<hGq zI3DU?Gv1m>GtD;kC5|D=)^bRIrIH|s8Lva|4Ya1ZdI4WXQd<*-U3tL8QLCJ*<DYA~ ztV4jVOsbJlzjPGQR6)fmIgF8<dUNd1D7;nT4)e!UqNNFRNClhOjD5(Q_IXtUE}<X% z;z?nolJRhkRC@xnl6liD`)k>;%>kao9$Ax`n(Ek;KIrJ5JsY`=3{FS)PlYN6CTHQv zbU~V>%ST99b@Xgq$(i1(U+VBL_l_0*YJY8^=Q(Gne#HVc;`_r==V#X3TP=lcib^Uk zJI!k}A;3P(Jxco>i+h|m_|ddvR#8jeeGx|DIHUSFdHGMe%rYfuh6$MO<=v{zkz5Ls zWu5TMhl~QbNUWJ>o%@9wCPlv3FPWwk*d!ECs$-)>T56^#vcN<q>XhlQQKxHLGv^)- zQ*#xChC$PjwD$ITRlZM!{V7(j<#5&AbIAI9Ns!JnZ-~8iFNmg7p}m72Tpg8mG{iXp z0DMHr+WoLH<i%QU4zom&Oe?&<K7z{#sae5m&&kp=cm|qn$bSq8Rm~qDp{=azrsJe( zxkIOo<UPxeO+y~XYkkgF;Xb1*K^okpZH02a5aEpUZs35>JDy+=-g?lB9t<?v<~gQB zh_LzS`k#8P8f{K)KRA126<H<WA+g>|p3+2Dksm5D&K$v@W)M(@5cfk*s*Nkb^+$zB z;<M9?41LWYt^|F~=meceo^gzgKNp5S+H97Xo3(mDK=?WChczxe5H-eF5aXOz3Ft^! zMk0j$&C||FR+mf>i82vRG?Sib>K|9i<wc7&f66P?*^v^Tm|i>PoMBD|4851T;*!Wo z5(NDFBhJk2yfCimZt}s-(9lp@aqGJv8+;0^pX5N=%UdB@A3~rJR~YWJ=)M=#w7Lka zrf_?~pMn88=w%Lb?co(0tWGrpIbxeFHCmD*Nx|}F?>eQPKj1@dlC75^bSee%`c*!N zi*32qVWMnKE>xwfln|nbAth{|qWOJep~HBh*8b;6ExT+=YNna8b{P?7UN$ZFS$ouK zYEtWg^>RHGdgKNvfNH*h&A@}Cdit!aGt7l>P|-a9pBFJ_r3ppjbU*Cy_&!F=^9G8| z4oGo4kZTy#<DQPCVB=cpIqG{z*dq_h)NeYt{4T+C8**sOA`@@&6ZHoQHvk<#&<&`h zHhGFdr!h)RmZZu+2LMJJIjDP)OsH)3oCA05hHCKa5f${US7%hZtT)_F)-qn8wN9|6 z4aT@EnAKt@>y>Y$@ARoK<2=Qvm<T@W+F)3cU@Ox<pziXy*^s&I!-NYZ9Esi`a)k*x zIEgx56j|DetQADu195B-cT$uLiVGJ!MYb{@y0l;3xe&_LpbmtXrI7R5Vh80=hjbWs z%^$Qx<3=*tq}i?lYqleDQL7R~86G?Y2vYXuC=b~2q9U;exJJSg^Y)gQ>rlEuDsaLN zgDIpe$`cK$<7|{d{*XS471#3oh|)J@NAV#IpZfUBPEvpIFUWo%4KiC0uX*MG9DZs0 zY{ylCWStLe<TMz&XuklqTk$3t2z)+kJOWfe;0sfYz3+f5pcTZs0n85U^KBdm;q>n? zApH!XjTKgr3c~w?GQCyE2u!C)TF2y3nP@be_b`MvIL!02F9X)GmC>-q$*|GYQ{@2Y z+T;y#12kR!xzZJUBg!Zn2zpIsSW-AHPrUx5;U&E9g3)UJmSK>e&l@nx8>Lg`KsJ}U z7@rCWijeCN?wXmymd1wj5|a>*z@!Dprm*?&8>5AJaAE#`lo_n@1l_EwY^vG*{S8A` z%_@$LigsEcA4t^^<~N(yLxhV_bRqh_k9mAOS$$L0<*{R*+&^U<N<-R=8L3;|0tkPp zO}S9*KIln+lA}?kvbOTgL1yPpj2?{*<0DG#;M3UP8vQ7Mo5hddr#XZGU5emq){bDb zxyV7I(LjbKfm>`KvlNO9vZ%p)8-216ZSGyUwM1-g$Ws`LwgE6in<pnA+sWW6{7DM{ zRZ6jjDB7^Y5)>htcCr@waV!-HsIY>mrCK9ANSZANFjZEH)3mUzdgge*_GokA@=&^M zMQ*?z4sJ4ww;^YkZ@=lp`X{IoCmjdNB8YY<N?np*wCm*33A&Hr#OzQdzYL@x+h<Ft znJP4r1Z-Z9{*G6&8dr8$WP8sKgYuBnm4xj(b>77pbb5V#&fTL35fkRqcaGeMaH-=g zgpR5f0B(C)HhW#lY9M7t2!)v{oih33MXWAW6qTp%@=4rpCnom)bteYy0Eh!0i)~^z zMF$B?4HWE-1f$JJ`=zCvV`YuH1V86h8*<Gt-B&so5O2WcYhJ16O0JWWp^cNt51^1* zcl)BgCFxE{0y6^u6&frdii#CH6^R4%Vdum5iYykRuA(8PLZf_>jc^yzt9xlnFb<ux zaarQMMqGhS)BH5JjMru?voRv^tfb-5e*v7r6a1T$>TeJQbcoZNJD&dLI6E^llP3kL zgWEG*jKQjxRn7j5oIcMcRiF5x+2mY#{#A)~H1nSz34XAkkeJWiX$@Xqh+>2P+Bcrc zen_%nVxANB3Xb$(<Gk+T>A*_Swf~?CWoLpu)kpZwT@*c<bd`48)kG)f1FC}uISu9y z6!btmQ%%k6=M)Hl#x|?mpnQTf&~hnKiujrk8{Wt^oo1at$Sn(H?UVV&RY(9J+qBz! zhzkLdZghWhGgYShm?>X3&RtS4qp46~nZ)2y-XLcIE>VU6MnMjf!qEoC4Wgo-I9rnX z5r655#&XS;Z+tpVf0ayY_-Mdfidw!3hf~6tbwan@`D^^YYrAeQ<g)Dxyf4fHO%;cL zmNGp!cfF#cm^#4uZ7C=+RWuPFIDi^#32iH?4cs~oGyKtlg`B`QqCMmoNi+#?)pBkg zw;Qpq4@6orB=+P&-i0eMZ}9Y32o*a?7#1I=TWcYMB^_&0!HO-EZ^x#Tc5&7w9LZ_W zK8}}iKKqU@(LV_N9m%G*5jhhb1O)T5+bkOF=P_*A2Ur&mv@mHykN%Dw41|gfmJ=E^ zc&q>HaScm-f!=vXhF<s$<+MFkRe_xdoHQ&Lv!M;qWKC5_g{6+x`tllgmM7^$f9NN1 zgOo$Lx^yVX*%OtDlD*&9$$2xU01K@k>26kcJVllg=8B8aYsItLn2p<`jpb86N2mH1 zdE6LduKWBOR!<{f#!YYPlL!`ee-$vKRLFD%rrIJJ&SLq}Bl_MLHwg33rBGJzmPT8- zPNv#Xl>7>NtwnPz?%cY5N^{7+EY_wS!JSzc)>D7rjU->fRD&*(3p^4}uiq&$_y{X< ze3>}&Q5b(=$V@0N(Ux{b`qn)DbcYdz9#C%2n3gcJLjg6U;Ys)#IUl)JJoeOcl<{J~ zi`;{kd!V~UrGyY`5~z;m91S<$2gKT#VgrBDfdGYE)IWdKDcc3pN4@%n`x3~*42wfd zGE{JN6~(;{v@EeO)Du^V?>zb1{cAWRXHhA|i~=~$#%ooi7IHD2#Dv{etp1`;_Pv%x z5jgi171?tA6^3pHz4bV93G%6Uc_hzRf-PyplA*3ei_s#<qN6dw#n1lBa^a4{axKU2 z&kKFVpHC>wR?x>UO&74na}K*#_ei@-KeZnj%#US>+LD9Tjz95qShiEX$1o!|Orflw zQ%$lw5O7#DDGy%JfoY3-Px!OQNVILXJlq-tNOUo7(3^E;4vj0?y84@T*Aol{Z});; zHGqHvDdEDJW(1ZEwTTL2EtAE3pvjzXHWuWxRd&_@bUi$41`Rs~A{H1Csx4umx}s8v z`gcv1s`!u0iPC_9=5<U#<KH(LECS=F3&YcPjQeeKvt4I>kB`Ti3&z|V*3C<kqxgC1 zbi##|Nh2$&tg$QGnr<%Fqf@D46N?ua-VFuKNgc4sTAbOxi1<OSsJQ6MP;%oMV5dZ* z<AxrFoS3zoJ?)Tc<JWu|WfTT17NWXr>4x1u5@pxrW&x+A4r}6~?$2S@*TZoeM7=<Z zv}<$p$Xfc6q(7PF*J&(WR*x8i$M=mJ3%*=1?#sG<TaM?vh^@i1h-5*39E_h?vxD-p zF;=3ylb}S}`r&V>MdqyNgD&W3R{Pwhc^X&TAy5KEx_1WA=Dxpr$@rMiVH!42Y;q~) zidPBRnj?bYwQSSH4O5DHqWKL|03a!RN*hv=RJ&BHJ?DUB<|$&NCE1}9$t@NDS&Fl% z0fCEwp2AcN47U9!zJvBnt(r<0ik5p*hh#;1d>}EehsXq-KUpdkwnEXF*D6sTCR|5k z=r-crDpq3=7V`xZ@9%g#vxH}VlT=;VzO=yceZz)ESPS{F60DX$g8^qv2sS$wnN7se zED`+P-_H=$s(Bfz1jz%$AOF5TnIYex|LxABFJn$Lu7&UxLfu6x=yFa(6nRNAL}$M< z&|lWN+91PAg@9cx(YVpeG=`R5?NAc9&p2{_-!e<X|0fAB^|svXgtlv<fYB-qd6g8$ z1m)3ZiWB0vm8O;H6iY0Ukj+QT!-2Td^~#K4bOX&Ghk@p}TIznKu}VvW70_dknyS5? z#3DzFl39jDsrxyhc?MTc%C7w4-*}bf%W%1xh2j_Rj|_yO_qTtwBFPj`F`DQ_lt#|{ zsv=vN^3^%A{EO$QO7XJ=Jrc@{Fk-2Y{b{%yt<{HfEl_0{dRna<Ui<Pe2s##tSz}Q! zZ9Rl|5N7;uvA0fT{vuBBWqy77qLR{UJCRoefDxGH&9|3^#~IT%aiWV^?ZN|UXM_Q| zMFswH@Z@&jc?#eD2d^9}()Zlm%@L}2fP}ejKrBiS1YxdD%f^|+XDtZ>e6y)Qk+hjX z1c8~EVaRR^n~L*md>K8cjZLs|8$$!Pih)96;n|^b+8Dd4!@}n6+dRd8Ur%njL;@b_ zA(qp?`e8D279}J2ETz#}$ye2)Ww(|Fvjd1#vCFc`b7B0<RcN!R8Kr^ZMLC6y(n9nc z&Hb4FiL+iws1mM(pj){%yMav_$JpgY6b1D5_xp@}$(7J}_E{wthuI(W+J3JJK!z41 zQ93sh^o$}Yiv6UVM7T<ss|x9%E|By-V;3fC&Ll<H>2W@QZs&T8UM;$aAz4?<NWvpC zbLPGKo|yTrs7(uI)aTKYtNUU;+wSK4SKJ|*v;Z0!RGBDpz&F~wxg3Iexe%ZR>liQU zyZG0spmSntS;<UY{(3o6`xZ3LcrpQH+)?@{4O&o^Wd_&oL}uDSJxN+D2$(9jrRjCF z6veEYmG~(FLs^qI1B9&<-IqDFav$T~b{5ll_BgOv+!M_22a|Lr%)nR~tyS*Y#Yu9J z8Jgkhj%ahK5rizw{rY%xx#v+t1^{79s(s`6!JsYbZ^1Qep~I@@QtAK~oHYZQ&IcPY zum5g2m1Q*EbvK+4^Y$(=KAVe~Q!Vdko_{I@0p-(eUv9htXNj8cB@+06UA<<fjY$mr zLOxsSt5^|Yy+K8-ZjT}(_$jrhIE8+mI_`qS_dYE6frdX1%<N)a!N(1wl^`f4vRzlC zMU4mZyyx9lF653^Jn*&ek|XE^d}nlFo7RJ9(rGDjnZ8cc9<^xP_*}$yuyA~g%aP*b zX}Gpj@1jy-Xee;_@?O2xv?jvy*Z?db3`s*3YTG$|!2zrS&43Sy!`+$nlOV+n1ckcS zQ_b13nV(z!;QQ#4Q+S}D@PU}mhGZCDg;!cVuJ>rw#nTNr3`p9ZvU%(!Ty1C#pY^X_ z*3iYrzsc{DQBKk9`x>i~^jKvhm}MCiUe(~(=yOk$Mz>7R8BVU$uj$MbEnmENoGMqr zmX!T#<ec;09|yzY#J*_C?wsqGZ^6Z>(ophxh$3Dpg250)Qr<!Ga4>a8Uy+u>gdL*+ z;+37DiTH|F_{BJ7o1D~kGUp%tCooF|5+UsXrrj1vC>#DYGY1~83N&BGvJwt8?0}=q z(o~b69@ukPJC3SEJYTmLpc`J5bi@q2EFjy1*pl3maIWQ0SSQX#WfC@YEab%y#&n#e zqTNi|+03<JRO^Zgx|{N}4!7kGQ&Q`cM*Z2PDdF4W&XzW{19+p_EKXiGjn=xp?+w_q z(GKsPVkhm<=NRE)x1#Mee`^mg$5`Mx#vQjuc?4NEfHfigu?`t77`56g*Kc>bBbais zY0uw5AG{{+#oRGXt@Nuh-dkBQ@IA79eOcjw%>@<i3Z~iaf9X`8QBdR1ChW-b%ReJA zB07Pb6ipT;MaQOAFb(AUyxNkh!Ra~Y0#FCrgs~>SnD0)R11mCoA|8xQu!Swr)}t;@ z=9^pcR%c_IsX2hV22Kyz-Qk`&>D|Npvb2Zf-%8PN5Lqh6KXG;5ygb3Bn#WsKGDP9- zX$kwGY*E-FRF~&mqJBwO*Q@=m5E;GSD{G}hZ!M%SD9mK06j!a)Wa@he#H}zb3-5z4 zr7#24&wlB_M2#<^sr+`I?r%PcxeU+65}K+ILBP(gw1?2)B$Wf;bXImM*JJ4+8}L&Z zR+l^Ws*v}~wNK|f>-sGZ^nAl&E^4;vD{qoXy#FPh7-cBV?hwAC!5$0QhVnN<oSS!k z{qdkgNsBT5)l5ou{w6HrOgVSzM|Z-EEu#8`iI95`G65@0P}B@7P8_F@P|u$uPn>G@ z{X{H@J}!h^sh(2E$E~?gZ|=r~gPXZ<7QSIlH-zrGVf}`Mznr}f%96kaz@)=CRsO+K zEo1?$^Li}UNU1dWf_E4iiwOnWvda(JI<+46Hg(wUv<|oaZSAgJZ|la$({ODj=m;9D z?C{;mQsQTaKqSzn*EcMuxl^p9^8#H!f&j@LBIr?emVQd6fvC_Ld{UI3dKIyLdHVB2 zUvAQtT_roo%tBV_vELjU*;vsZl31=7kc*`(4iUn{MyYk{vhWWsDjhoxgU2uru<n>B zgLNd12rgvar|l(vs`hx#`gc{eK{HV?O`(Wt0SMsZrZeHpp90g-Y#@5xR7jcok5im; zyc}p5;k6AJ0+79%5FkYn%;9GbwPE04MxReBVPi`;!2OnCv`fg^5OiV$+QUVz*g7M? zObY<)Xq2$v@E1z71vtFY^ttvZwiiM^jqdmAb!VZT)DPqM*Jw=}Oc(5kC052Xzf-yP zWL2Pf0-va<zQ-5-Ytj2ka~>tXhNFL1bz(oX`E|JNYd&WrJ6PYYr-7)0rn-eJUSHc+ z;1hW~udm9O)givOEp_*2;R<96i%r`$_?r1tS}i1TwNv5yqM?NMYu=cmAMByBq5&qB z&L~p)&&|na0+9?~72n$AEJJegr>8~WcHY1DXOyUca|BHd)05=9@6cuf5#D64>NUYU z6;jMIfFH`XlS2fO%pIZ*Xvrs$<gdExsc5w`WK-q(s`<D9giO=Q*FH5HKFA(a3Tjt2 z@qayT?{lo#J<quSgMjfKQv$_F>_(J)3`u~N<YKV#i19<7Lz`!mmDKH6n;I$R!3R=H zWq=+)%*=xTl~;cy9u+z;zeIMJ9ZEBjc66-=`;DgsIOx?dqj26)c#(mBpTSivs7J}M zsJCq|g8qmVGfF|3EwIJ+FpSU<!-<2iZVyE+5zCOKCiH98xCgwcgawLwj$cKuahk28 z-PxDjha=ZmzT48MMfoDiQ3&{$@NhKf-tiuSfDM}6qyt8>qY0Wn6!Ue!2cpiYIw{l| z`ZhtBGdUWp7%URAsDK~fd>u_&i_xDfZid-pjTkvvWbzvB#eb|LiR9!(`^e76_+$o> z$en}V7>yDd3ibvED$5jhH+1uL6HKZ|@(=)DV?_ArMRxO;sl1>>`y*##;kVb{CQtTV z6+J_07UR3jZ)m^xFET>*U3vmmo%&odOGZdqMGMc3!Ypp@j$<W46%jgknTsgc1vmCM zp!vSxMP*qK`lM(0+D++q$N_88fwC&`etvg-=l!wyn2x>vr#9XU{vxH2&tGp)0+IU` z=EULF03qkZt?V@(mK@!``bXZrFu-SZyiJ9?i^VV1e{4em<a~-VsJ=X^p8){bNCQAh z0S>Z@jSF3auJ|Db2LOWN@Y(_j3F<K$3^Anuk`{^z=<kEo$t}}9JTlOhGaWh@iZt$g zIGCVE6<hPjOjaScBmn}}Fl8qrg-~~7oPp}(5HzMGkPf;mHV@MxX!(0vA<8R4>4csy z_{7U%@b!t6^>kFyAUVO=Z?ULMcmR-Qpm47tASp1cR&X)tlL=*HzB$&l&i1%{>w28e z%drd_sN0xn@v7;lG$3kH=^GCd4<Oj}w>QFV&>@1;GJ|5B%#_s71(qPqn=vrAI>ieh zj`-lEEO(Hd`n;PZMIc!z`%7{*A;4wgp^Fs(I7Ajg(F1GZ<~PHJ?zg|JZkD?n^Uevo zd8NGvV0VM|-bs^MVXP&A=_LJwKa}})umJ$1Xan`qGdYBLatwU6?ZQ3va_9sZJd;06 z(~{<VzZBfD&Ks#0x31ld$ZVJ&7b=(=VH9A_0|v~*0g}$jg(%nT-O|+FFH{rsMWC%% z0MI;ESPpMx31}ivM#UN`sPA4X-iC;duXK30+uFdu)x-!xA2;O+Hlqe~^Ysv_yQK6W zazF5i^Z&^(oELJ9R2#>e4?U6Kwv*N=(N?DI1^&|}3u?oh#(p0f9P#r^@*5_EX$Y{r z?Akc%83N-I?i2ajJ^w0Kv$Fnpujg*R#n)=W-0L0ON)uzlO1KLL9RalmOrq|h$g-ko z40Ga8KZTHUoiu9wc+I-}zHQ|tptLvy?AiNkosE-?Zh!WlT-E^~@|$U-x%f;rStPEh zA)2n&9yzkc75QS?v{9=J;q=@<0*q<C5NrMyhc_EEh7#?$uKva!gl8fii9h<CA!j>K zhX4keo-LFt$uX7rDOH+tEcH7`EPj-yr)j<AO?4;ERh|%-e`#es8ae$pTuz8$A5+|q zgx>FO-_yU=#4y9`!>Dms00nckcsQ8*uJn8_Qb}~kq;xfuh<~loPsV{>nr^KHKm%dW zNJ5ouF#+U~XuLielplTsg7sx6S_lB@Y{Z*t;GMe)v96DS*g05}sM!9gVYg_<sa-+- zqGf(G0MqjF@`<(NdkgR8=5N)1o(uUDWl_je%tx|N0f|uQqD<paIo4Ed0HQ#620MkC zSr+;u@`Wi-<DV;p&UO;#VtOSoM}wy)crV!OWQ4?#s*L<TDJQu{Hnpl`sS?GVB<)qk zVYe^!oGx;TUm?-oFi`GW8|ElRwbM)0`l;bsXLb%gKy@i@grAo!^Qy4%4~85l$<b=7 z-^EhRJ08Kq;=a%5MXg`IYRrn|A4{)%e?v8-`^)@;j=NdUwVXg?h)-XK=aJtruIE7F zzuwPq_e?6g()T19r7&WgzsmTcXly7r02D%08Fd`8O$nbsv&5D2uTEzLJFSaqt)g;A z>RiV!ezLM|S@oPBJb<MnvP4UKSXmEF$tI26qW|g(5F$&IiL1C)?Syi#NT71T1ysM{ zP;4v6*i6zyx@f}XWYJ;g!uC&BDRNQpBP8e0#YB#dD9#q^jeYw+Av-HHXY|Nh^+vOW zP;IHgoJ7f#o{!&P<mhNJlKmKKK{O%Xm7Gr^TMxc~Z~6@ZYhvU98_wKoSeTOvrWe&$ zz03>014W`)F?@FlI7xx`G#4)k1{(1wu!w1<*Hb|nL1xsHX!dC8MSY*DCW~W~XA(R* zuQREa-#4i=niVVnHD#(xIY}i;ld@xaAAq9P=ws`bC1knhe(KtaB2f+qeYkY)I;EF) zkx&MfPL)#aotmJ(P63Z7MREc{V71O}4%7X+=+t@l<d)YxHmev3I2WSc@I8m|(*VI? z0qc=jrqj#qI)OlxJUPrr=FDt8DL`z@-jEuDt+w!*f(1Ntt{5>8Hu8yc!MPr-1X_da zP&UTVz{L4mNp=XUuIqu~{$ycVqIvcDMNrQ-3@QtC*V`qQCUgq+9%$Md`XP*r-R@|p z%c7ujAdiu=gUc2e5SSW$cXsC7XI*12Gm8OO5Q)z`CZiK))t{W2RLLPLfpGFUkl?ko zLPqLveQx+Zc-pM&4xOcLOAEArh0pwCJ%2B;hAKNGug8klh{tH<!x){-5r!Is`JUi+ ziGx%#9!hwKv`+xF2YBar#Sc2k`m8-sm3Gd#zeuIzHP(thiLzThQ=LV%mKp%9)14GP z`8S#KssS@Xf#G6L;pZ0sC=ut?H?Gkl7Ftom(V^rKgyL%g$xSQGa|}LnAgS(;X9=H^ zx0Z{3v!4DpPmkjV;-!wCuG(hz=lbI8$y%crD0r%8P=i8y<hXzG(OM)e(MqP6v8Shi z;y=6=Ad5@wr%MX``2jn6>n!c3X*GjYZFNl)*zSj=ImOZ(yw<YBVBfRj_@I5_q-iI{ z?YP3=Q_~%7LwS;~c$v;kh0p5F3?j?#+DQUjv3}CAG1s3F4B9ZK?QX;bbD)33d--Lz zYgrx_k7<D2jX+A1rpel?*^=VOW_p$E&|V_}ejx_r_I`ZnziiCLD~a4YD>VM{tk9_0 zA5;1ES2CyXHH8A?bQGu3u5Y^eKu&DP6a)2^JX%^{z@g0mSH;SuffMX=F*be}O_HUL z^KqQxW=fE`{1*l>xpk{46a)CvfFPe_TTS8$NWx}n7tXz~oZ0n(86(!CEOXv*w}MH| z{NvUmX(y)Ni0+7I7Dfk=M}oC+9^>J`$wHByKCQOw$m_h%W}kk$V1Dle&{r$WgB5tt zKvJ^5NX@%H?P=g*>n9Lp)4r1iN|u{JK6Cis#|7#wzzj&%Q5n6@VI;L;R6FY&<9Ir6 z3NUm&1G%v$BV1O15K%oTcu%^=G!Lt_@K+Ba66xEQX)mE<9!<`JjzAt?H=^%^Z3X}W z@{G4bil5g!@~g;r-eQt{oK)L`z65i)qUnAMoJDUsb`E9rnD=!vdoSo`Ox_6wkBeWW zcC|mMI63b?sPE@e{Y17M)11)MgJJ0Cw;Y%**DVwNw0of!bt0w}4X7hg)vjqzhn;_? z-2B%`VrCt8UwQP(RpRB_%%FB)#AgwZ?=@(3mUs2GT|2<Y*~&K;!En@oW&5Op%9F)r z)^}zJWA(n~TpR+n<wVsXuWLRJgYAM95yU|NW#oozW*Ld`ECWOE@mj0h-x{}b0jtC1 z>pe8LQFOKZqrAPG>Pegx^ekw8HRX3e*-x^1N|1T(w-C%QQ?<5zFgzIg-KtFwyV?_T zO|SNO;@NY5Gek;2`Q_Ubd`bdSwt8vDvK*U=pTxTi-1DX%Qt-Z>TwkA8e{?<Y1Xl<$ zzrH%$P&@8IZ1>uT1{4Yg7**TJg_4bjPF;WW5%ls0?GE7bh>R3zku6FqqG5ad;nwpe z4q8lI+_Y1;Ogb4F@zCtFizIA$i`7cFRx|K7ktYAODq0Ti<u_--zMG{7+;ms%M#{PG zM6AX|sRcDZJ}wc(N@du5nA7=mk5T<@AVm}&F3K7Hhm>xPIOHrHS=E!JSwB<%tdIId zUWW5SbvH%Rw6qMOb)S2S8IXeS?1mNUv0f;|O9l9WXS}<y_^G_mndsWn87{hwW{siS z=h=-8Xfwd3M1Q%R_Qkg5=FdgZ1>5p!^)fk)G#u@XV-}X%r@{Pc)56hr0#i}2+}u?1 zvBD;+o8$D#WuYc5v%ewnR-zNH3?8oB9%0yKF9TiI_q6_+oA@b<R5lbco#zczaLpE% z{16GSTU2OTwEY*>({yIrnK#7rngjh#(v$a>Zp@U)+XhJ!p^epMm~ry>!F+<TlfOI@ z?pR`l)~~h2=ncJBMEc6__we5ITlDM2zT@oUmqjmR8X2J|WJ{O*xbZW!VFPujZj{u# z{98BS_2}p4HqZ46pFQ^Z3&n6ZnIANxvHF6BnYtCFAH&R|1qW?|X3;wcLvnIs4fJ{t zw7bX8O8@g;035;AZ41+KeJCh+QT+rW<d+i9Ln>?T7w%H|do9srlV{bh`?Q?NUiRr( zU@BX;q+D5Z^@il?11%eKg^#*}W*U#&M0OH%Qn~(yxYh9-jV|<0l6PM?Z;wuqP<s`$ zwJU=x+)&E7EP-D7jcOAs<@IRNWs9R-jdx&#*5WU{SS8(az1S*8f!PyMTarN~$YDE& zc_o=TUBHkX%&;^Bv|M{G{^Be$8RGZt2Oo>^UhE(`cC0q+zTy?DtRXR!m=*YrmLt4Z zHkO3955yL3*o{hTx6H$He>2Vr6%_du_7=+g(l91Ev>F-G-9R$dUV70e+A?09(vBK> ze?&vOr8K*-mpF7@XS|g}5A~1&sO49uT>U<Co_2_G$Q1B>^4e(e_5blFD6sdl4_f6w zNc9~g_uDKw%@R-9IR5B9=*G}<?fB@?to|SK5ebh++a5&!K-$D7aCCydw`aYi3qe7c zN{#;VqS3)EB7${54@OA<K>a9$*H!0PeVcL6TA#A_No%xZKB&$9;l?Cs#4E8#$HZ<1 z)vsYOXPy*~XFt}ttMyo+^r=ERbrFY9*pc*Q#Vg4#tBi;I?+9=KsCFNK`Uxfkk~y`} zMwRu`woQq1E5~unKzp-9s}N83KUVPr=Qeshwy5uY&v+eoO~tzxX@%cz`{{jy(*zOO z<DQPP5dQ6EgNPPj_EGBmBnA<?d}B<ip6tHH6vcRtPL3+N_F=gnf2BAjhqR1~jez2d zFHOihQYlC*WC+NOrmL~OKWmn@_1nH}c(hcguG+IGed-+18H~YKU0d7kdWV4V6iLo$ zluoC(l)K#_NMzS<zv<!X$TF=}1W{E{vx&35L+-5ed(V38OVpZcN&l<g`bX9FRsr#D za`t(QAjP9%{w)FD>;FgAS4PFvG~HrBg9Q!l5(am72p-%$=-}=eVDLb2cXxM!yUPT3 zcX#;adGF6VYt8vHXU#cXUA22xbyf8_5LIVxZ>Ab9NL}hXN<aL!iCMO3G#PM{Z5*Nb zB0)~x=T`Br1EReZvJVXbbsT~l!fQ5D3G_rYCdLHpXYTKRZ7U92NjUm64{0!rsC#1I zpXsZzKVc<deV(;WCK59@VuXpV7eBQz860qmawcc`mE!0)2GjKE@KN2YeU=Z6eHRx6 zngGow+Ao4-FxvSNJ4$k82qK8vFQ+y=N{;0HD#EgJ1;aNSWARioI^NDJp>J92asy<k zC_w{>g&tO9HPan62W_zY?gDp-)wvkp8I!2r@uP{NFjvhRG>_F#Y0*;8{ZQ4&flML) zqD&YK%Ck~|iNGHu1}DQs0QD?~s1o2HtC$)+CsGNvsTPPvE4Chh1&uiDWn!DKua_F} zw`GJo`n7sc=XWgyOkt*hztKPtF+&I^Z^^JgH{RBjsxfx4{l5*?Ub#M#hYCgHpYT!` z<`_ntJyDTQP^FtbAc*nnfY|##5ul|wh$DFG{#$^-0!%ARJ4`E;e{;9R3mM2jr^z@Z zcc^JbHQccQvBL^@3a#*-z(oKs4vc!277kChBl3qGSN*!P&9o^IgUEQ??`_Jza#&>U zIrs5QIMwx>j^A;wdE6*~!x$J^8Ht=mJBUM+;i5o@tCOg*ypmZ>E9@5U*I$X2u159O ztp3H}#%AI)c5f#Z;T{5^_|I&WmHsW?o^}3cSO?LRail@xj^%*}+7I&RY&g=m&IwEr zjGLyd@EPcW1%JtnDZ>q7YYgi^nE-5zQ#cV9<3YdgE7bixUq6jAex6-be+Eu5ikH+h zZMY|%*ks#&A)mOKK$)p*hk<F-gKDXYKCzf~no*jWa;QVl+}h{00saM>2|NAbjS^ws zhxH(U66lc)O2iN8k&OtI(o>V!l4UTp;tex_L&L=?2>H9rGnSeN)pfAvnN?JJfZ*^Z z%4WmmqE!INds}iJn^<XXc8>H*ppn^avBfNFXgxlnqaY*Zz_mNferZ!V-5<@OuM>R6 zarVTa)a{89n%DD&AVQ_eI?Eq3LXT|=zy>q;Od+6xo>MeID@i3kWH=pI?D&O)ApQ>Y z6N8%nAU(WMf-J9Wh&u%&fh=Yf<@^+jk%-GB(jPH$R(T($Y?CvFVNK!W+YMIrV^6W- zLKYuu^$-=2XzgJM#^E61AfoyNs$mpa=}{dU256}M;$;g?LQzV5+Gg8P<*wZ3=;{ip z_*;o`ScvG%V$2(Fi@Gda{EXE58A>4fMx4F^(hY%;k@2aEdC_{v*xdY@$im!iqoR6+ z#xO-UCDj)9la}$*snv*+V+H5W#HfZPoxq@=oZ21K&U&TroEb~1jqi1W%>KbRslSc* zuT*cWb8rte(RJ4}y>6Lfsa^`3;V1*3`Ef13b=LyO;QQ9rqq=8w$+#pV0D9-G%?3&o zxNzj;CXsd9@K_9pk&P^54oiC&W^ZUT=Cxc(+X0a*G>G12g4C4TEhGaC0^fazv6J#R zK6C)!iPl!hp&{8I1Ney_zg8(2YwD50X58WkZ&j#1rTo@@y*g03(6j6`-mno@O%aD% zAwWcVMD8O}W>R9tbw71=3QmM8M+z;~KN{+zip@jBpkOH%n8MpX7+9uK*l)6(lhy6D zj^FU;6VgpNXR-45rf~EAgc7(xqf+6aC!PL7C|)9^BP1Q9)z6VWad<_7W~d&>MafIj z{d>Xfsyss{$MDU@l_3f223Yc;Ubp=gc^$ht4d}%PRj06lmbB$onCr#<DU)uH!68|o zH%WkE{w0WBIziSQ0Y%12OSiTlg)u5nC1x>Z<ddZ0@~(Wp4ay%G>q-vY$~=}%39sN9 zVMO97&L5qjX;U>sa>^j{ad?59K71bJQV3q^FmagjhUWj)((AU^DxZ4TKj4eZ)#~y2 zqxcZPG6}l@^m1uqe&JKJ{{0m|%OhM9$*@NEITe)p5)9pMeFkdmG^k#)V<=4FWsZ2C zJzib4Nh>hMH@BL#Yov~>=W!n<%oL~i8&LydOyALQJg`^RNgwARh(3L+|GPd)?ELf( z$)YwYC=y8S7p;PF3rw#3<!xh<jF&Cl9kbW2W}#MP!SWZ*tcQ$(JP4jM6CekTC}H-t zx?&pzjbtQBwHS{PXp`u!f8R#|s|C%91Vsl@_#;mri!V{tryO6=YmXN~4%A_1UtH#i zkSL%-#)JiGycpYythrIjILnpMMc^e(#3+h`$l1U-dvFwog)F#ZeXHtp6=~^K*?Nkh zrTtc3b|JH?1i*pNGY!)N8>5O0!daFPW|tk&j5{wuin>LvbOlx}W+zO`!XUC~onRKC z&k_X-)~r`3%o)~PL2l)|!ouFygckKwj(SRDi^%mL91|_fFTfD80xg6U!{kgqJ^IG| z(DlEM>Q+P=dQpCr!E{}HC7+)8(Oc(Vs`JXM0Exb)awBBW6-`DMJlWvBt!c6E!!-I< zGFgOQFv2-Qet%&B_2+&5O;e~Hmw@t{W*fi&WqWP&)a!}S$af?3R2AK8Icy-#YZq^R zwxkVq{X+$fG31z5w5U_aNfxuS>#2h+nPv_H*Ts#vQ`qIinZ@tfG-Ja>7|7ZreAuj8 z88SJ3G+>uy;TVrGZNn8#H+3U%b7SQeK_8LFGeNe9FCNg?w3<Zk1al2QF^3RnN*a97 z_oRaM`wJ{!JkPaYtTwgdvY@xlA13`v3>1f@Q;`ZpZ8?boiGtcQ*G|<~-P&lf?<BEx zt*OUUhPx5-KE*01E)z5n%H0eoS_ITIh$>{k(F5ihdldph*dZ!9v+9_Hg{>!tvvAxY zUl+V+U;<0Z^wGba;D6PPDxx!ipRd_)zjI3tf(yqK&BBq)bvv7lQN{mLRH_1*?>xFX zfNp~Zy`%&bk#sULlSx=#1NeerKs!yurhGwVTvAy=Ce2L7AcbP8adFKq85BhJeXfoG z_gbJ?a&r7kgF#{LJx3{r?z0R%e1rfNgEF>NWjKqLerjqudY^IqOiFh4*xbBUeTN6R zo}OC<l!)taP91L*2!v4p$bz$9IT6hc$XT!s@ox;`w=ghkmE+uyvJR|TN@M$5MZid< ztdSy_C@EgrY9SPp2kGw7z-)qGT<6=RfDb;yfTXbDQq&A;IjAoPO(nV|h-Fz≥CC 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 ATmS$7 diff --git a/library/simplepie/demo/for_the_demo/sIFR-print.css b/library/simplepie/demo/for_the_demo/sIFR-print.css deleted file mode 100644 index ec89b1961e..0000000000 --- a/library/simplepie/demo/for_the_demo/sIFR-print.css +++ /dev/null @@ -1,35 +0,0 @@ -/*=:project - scalable Inman Flash Replacement (sIFR) version 3. - - =:file - Copyright: 2006 Mark Wubben. - Author: Mark Wubben, <http://novemberborn.net/> - - =:history - * IFR: Shaun Inman - * sIFR 1: Mike Davidson, Shaun Inman and Tomas Jogin - * sIFR 2: Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben - - =:license - This software is licensed and provided under the CC-GNU LGPL. - See <http://creativecommons.org/licenses/LGPL/2.1/> -*/ - - -/* This is the print stylesheet to hide the Flash headlines from the browser... regular browser text headlines will now print as normal */ - -.sIFR-flash { - display: none !important; - height: 0; - width: 0; - position: absolute; - overflow: hidden; -} - -.sIFR-alternate { - visibility: visible !important; - display: block !important; - position: static !important; - left: auto !important; - top: auto !important; -} \ No newline at end of file diff --git a/library/simplepie/demo/for_the_demo/sIFR-screen.css b/library/simplepie/demo/for_the_demo/sIFR-screen.css deleted file mode 100644 index 778e09d2b6..0000000000 --- a/library/simplepie/demo/for_the_demo/sIFR-screen.css +++ /dev/null @@ -1,39 +0,0 @@ -/*=:project - scalable Inman Flash Replacement (sIFR) version 3. - - =:file - Copyright: 2006 Mark Wubben. - Author: Mark Wubben, <http://novemberborn.net/> - - =:history - * IFR: Shaun Inman - * sIFR 1: Mike Davidson, Shaun Inman and Tomas Jogin - * sIFR 2: Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben - - =:license - This software is licensed and provided under the CC-GNU LGPL. - See <http://creativecommons.org/licenses/LGPL/2.1/> -*/ - -/*---- sIFR ---*/ -.sIFR-flash { - visibility: visible !important; - margin: 0; - padding: 0; -} - -.sIFR-replaced { - visibility: visible !important; -} - -.sIFR-alternate { - position: absolute; - left: 0; - top: 0; - width: 0; - height: 0; - display: block; - overflow: hidden; -} - -/*---- Header styling ---*/ diff --git a/library/simplepie/demo/for_the_demo/sifr-config.js b/library/simplepie/demo/for_the_demo/sifr-config.js deleted file mode 100644 index e7066b3613..0000000000 --- a/library/simplepie/demo/for_the_demo/sifr-config.js +++ /dev/null @@ -1,40 +0,0 @@ -var yanone_kaffeesatz = { - src: './for_the_demo/yanone-kaffeesatz-bold.swf' -}; - -var lucida_grande = { - src: './for_the_demo/lucida-grande-bold.swf' -}; - -sIFR.activate(yanone_kaffeesatz); -//sIFR.activate(lucida_grande); - -sIFR.replace(yanone_kaffeesatz, { -//sIFR.replace(lucida_grande, { - - selector: 'h3.header', - wmode: 'transparent', - css: { - '.sIFR-root': { - 'text-align': 'center', - 'color': '#000000', - 'font-weight': 'bold', - 'background-color': '#EEFFEE', - - 'font-size': '50px', // For Yanone Kaffeesatz - //'font-size': '40px', // For Lucida Grande - - 'letter-spacing': '0' // For Yanone Kaffeesatz - //'letter-spacing': '-4' // For Lucida Grande - - }, - 'a': { - 'text-decoration': 'none', - 'color': '#000000' - }, - 'a:hover': { - 'text-decoration': 'none', - 'color': '#666666' - } - } -}); diff --git a/library/simplepie/demo/for_the_demo/sifr.js b/library/simplepie/demo/for_the_demo/sifr.js deleted file mode 100644 index 0a8b1b6dc0..0000000000 --- a/library/simplepie/demo/for_the_demo/sifr.js +++ /dev/null @@ -1,19 +0,0 @@ -/*=:project - scalable Inman Flash Replacement (sIFR) version 3, revision 245 - - =:file - Copyright: 2006 Mark Wubben. - Author: Mark Wubben, <http://novemberborn.net/> - - =:history - * IFR: Shaun Inman - * sIFR 1: Mike Davidson, Shaun Inman and Tomas Jogin - * sIFR 2: Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben - - =:license - This software is licensed and provided under the CC-GNU LGPL. - See <http://creativecommons.org/licenses/LGPL/2.1/> -*/ - -var parseSelector=(function(){var _1=/\s*,\s*/;var _2=/\s*([\s>+~(),]|^|$)\s*/g;var _3=/([\s>+~,]|[^(]\+|^)([#.:@])/g;var _4=/^[^\s>+~]/;var _5=/[\s#.:>+~()@]|[^\s#.:>+~()@]+/g;function parseSelector(_6,_7){_7=_7||document.documentElement;var _8=_6.split(_1),_9=[];for(var i=0;i<_8.length;i++){var _b=[_7],_c=toStream(_8[i]);for(var j=0;j<_c.length;){var _e=_c[j++],_f=_c[j++],_10="";if(_c[j]=="("){while(_c[j++]!=")"&&j<_c.length){_10+=_c[j]}_10=_10.slice(0,-1)}_b=select(_b,_e,_f,_10)}_9=_9.concat(_b)}return _9}function toStream(_11){var _12=_11.replace(_2,"$1").replace(_3,"$1*$2");if(_4.test(_12)){_12=" "+_12}return _12.match(_5)||[]}function select(_13,_14,_15,_16){return (_17[_14])?_17[_14](_13,_15,_16):[]}var _18={toArray:function(_19){var a=[];for(var i=0;i<_19.length;i++){a.push(_19[i])}return a}};var dom={isTag:function(_1d,tag){return (tag=="*")||(tag.toLowerCase()==_1d.nodeName.toLowerCase())},previousSiblingElement:function(_1f){do{_1f=_1f.previousSibling}while(_1f&&_1f.nodeType!=1);return _1f},nextSiblingElement:function(_20){do{_20=_20.nextSibling}while(_20&&_20.nodeType!=1);return _20},hasClass:function(_21,_22){return (_22.className||"").match("(^|\\s)"+_21+"(\\s|$)")},getByTag:function(tag,_24){return _24.getElementsByTagName(tag)}};var _17={"#":function(_25,_26){for(var i=0;i<_25.length;i++){if(_25[i].getAttribute("id")==_26){return [_25[i]]}}return []}," ":function(_28,_29){var _2a=[];for(var i=0;i<_28.length;i++){_2a=_2a.concat(_18.toArray(dom.getByTag(_29,_28[i])))}return _2a},">":function(_2c,_2d){var _2e=[];for(var i=0,_30;i<_2c.length;i++){_30=_2c[i];for(var j=0,_32;j<_30.childNodes.length;j++){_32=_30.childNodes[j];if(_32.nodeType==1&&dom.isTag(_32,_2d)){_2e.push(_32)}}}return _2e},".":function(_33,_34){var _35=[];for(var i=0,_37;i<_33.length;i++){_37=_33[i];if(dom.hasClass([_34],_37)){_35.push(_37)}}return _35},":":function(_38,_39,_3a){return (pseudoClasses[_39])?pseudoClasses[_39](_38,_3a):[]}};parseSelector.selectors=_17;parseSelector.pseudoClasses={};parseSelector.util=_18;parseSelector.dom=dom;return parseSelector})(); -var sIFR=new function(){var _3b=this;var _3c="sIFR-active";var _3d="sIFR-replaced";var _3e="sIFR-replacing";var _3f="sIFR-flash";var _40="sIFR-ignore";var _41="sIFR-alternate";var _42="sIFR-class";var _43="sIFR-layout";var _44=6;var _45=126;var _46=8;var _47="SIFR-PREFETCHED";var _48=[10,1.55,19,1.45,32,1.35,71,1.3,1.25];var _49=5;this.isActive=false;this.isEnabled=true;this.hideElements=true;this.preserveSingleWhitespace=false;this.fixWrap=true;this.fixHover=true;this.registerEvents=true;this.setPrefetchCookie=true;this.cookiePath="/";this.domains=[];this.fromLocal=true;this.forceClear=false;this.forceWidth=false;this.fitExactly=false;this.forceTextTransform=true;this.useDomContentLoaded=true;this.debugMode=false;this.hasFlashClassSet=false;this.delayCss=false;this.callbacks=[];var _4a=0;var _4b=false,_4c=false;var dom=new function(){var _4e="http://www.w3.org/1999/xhtml";this.getBody=function(){var _4f=document.getElementsByTagName("body");if(_4f.length==1){return _4f[0]}return null};this.addClass=function(_50,_51){if(_51){_51.className=((_51.className||"")==""?"":_51.className+" ")+_50}};this.removeClass=function(_52,_53){if(_53){_53.className=_53.className.replace(new RegExp("(^|\\s)"+_52+"(\\s|$)"),"").replace(/^\s+|(\s)\s+/g,"$1")}};this.hasClass=function(_54,_55){return new RegExp("(^|\\s)"+_54+"(\\s|$)").test(_55.className)};this.hasOneOfClassses=function(_56,_57){for(var i=0;i<_56.length;i++){if(this.hasClass(_56[i],_57)){return true}}return false};this.create=function(_59){if(document.createElementNS){return document.createElementNS(_4e,_59)}return document.createElement(_59)};this.setInnerHtml=function(_5a,_5b){if(ua.innerHtmlSupport){_5a.innerHTML=_5b}else{if(ua.xhtmlSupport){_5b=["<root xmlns=\"",_4e,"\">",_5b,"</root>"].join("");var xml=(new DOMParser()).parseFromString(_5b,"text/xml");xml=document.importNode(xml.documentElement,true);while(_5a.firstChild){_5a.removeChild(_5a.firstChild)}while(xml.firstChild){_5a.appendChild(xml.firstChild)}}}};this.nodeFromHtml=function(_5d){var _5e=this.create("div");_5e.innerHTML=_5d;return _5e.firstChild};this.getComputedStyle=function(_5f,_60){var _61;if(document.defaultView&&document.defaultView.getComputedStyle){_61=document.defaultView.getComputedStyle(_5f,null)[_60]}else{if(_5f.currentStyle){_61=_5f.currentStyle[_60]}}return _61||""};this.getStyleAsInt=function(_62,_63,_64){var _65=this.getComputedStyle(_62,_63);if(_64&&!/px$/.test(_65)){return 0}_65=parseInt(_65);return isNaN(_65)?0:_65};this.getZoom=function(){return _66.zoom.getLatest()}};this.dom=dom;var ua=new function(){var ua=navigator.userAgent.toLowerCase();var _69=(navigator.product||"").toLowerCase();this.macintosh=ua.indexOf("mac")>-1;this.windows=ua.indexOf("windows")>-1;this.quicktime=false;this.opera=ua.indexOf("opera")>-1;this.konqueror=_69.indexOf("konqueror")>-1;this.ie=false/*@cc_on || true @*/;this.ieSupported=this.ie&&!/ppc|smartphone|iemobile|msie\s5\.5/.test(ua)/*@cc_on && @_jscript_version >= 5.5 @*/;this.ieWin=this.ie&&this.windows/*@cc_on && @_jscript_version >= 5.1 @*/;this.windows=this.windows&&(!this.ie||this.ieWin);this.ieMac=this.ie&&this.macintosh/*@cc_on && @_jscript_version < 5.1 @*/;this.macintosh=this.macintosh&&(!this.ie||this.ieMac);this.safari=ua.indexOf("safari")>-1;this.webkit=ua.indexOf("applewebkit")>-1&&!this.konqueror;this.khtml=this.webkit||this.konqueror;this.gecko=!this.webkit&&_69=="gecko";this.operaVersion=this.opera&&/.*opera(\s|\/)(\d+\.\d+)/.exec(ua)?parseInt(RegExp.$2):0;this.webkitVersion=this.webkit&&/.*applewebkit\/(\d+).*/.exec(ua)?parseInt(RegExp.$1):0;this.geckoBuildDate=this.gecko&&/.*gecko\/(\d{8}).*/.exec(ua)?parseInt(RegExp.$1):0;this.konquerorVersion=this.konqueror&&/.*konqueror\/(\d\.\d).*/.exec(ua)?parseInt(RegExp.$1):0;this.flashVersion=0;if(this.ieWin){var axo;var _6b=false;try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7")}catch(e){try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");this.flashVersion=6;axo.AllowScriptAccess="always"}catch(e){_6b=this.flashVersion==6}if(!_6b){try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash")}catch(e){}}}if(!_6b&&axo){this.flashVersion=parseFloat(/([\d,?]+)/.exec(axo.GetVariable("$version"))[1].replace(/,/g,"."))}}else{if(navigator.plugins&&navigator.plugins["Shockwave Flash"]){var _6c=navigator.plugins["Shockwave Flash"];this.flashVersion=parseFloat(/(\d+\.?\d*)/.exec(_6c.description)[1]);var i=0;while(this.flashVersion>=_46&&i<navigator.mimeTypes.length){var _6e=navigator.mimeTypes[i];if(_6e.type=="application/x-shockwave-flash"&&_6e.enabledPlugin.description.toLowerCase().indexOf("quicktime")>-1){this.flashVersion=0;this.quicktime=true}i++}}}this.flash=this.flashVersion>=_46;this.transparencySupport=this.macintosh||this.windows;this.computedStyleSupport=this.ie||document.defaultView&&document.defaultView.getComputedStyle&&(!this.gecko||this.geckoBuildDate>=20030624);this.css=true;if(this.computedStyleSupport){try{var _6f=document.getElementsByTagName("head")[0];_6f.style.backgroundColor="#FF0000";var _70=dom.getComputedStyle(_6f,"backgroundColor");this.css=!_70||/\#F{2}0{4}|rgb\(255,\s?0,\s?0\)/i.test(_70);_6f.style.backgroundColor="";_6f=null}catch(e){}}this.xhtmlSupport=!!window.DOMParser&&!!document.importNode;try{var n=dom.create("span");if(!this.ieMac){n.innerHTML="x"}this.innerHtmlSupport=n.innerHTML=="x"}catch(e){this.innerHtmlSupport=false}this.zoomSupport=!!(this.opera&&document.documentElement);this.geckoXml=this.gecko&&(document.contentType||"").indexOf("xml")>-1;this.requiresPrefetch=this.ieWin||this.khtml;this.verifiedKonqueror=false;this.supported=this.flash&&this.css&&(!this.ie||this.ieSupported)&&(!this.opera||this.operaVersion>=8)&&(!this.webkit||this.webkitVersion>=412)&&(!this.konqueror||this.konquerorVersion>3.5)&&this.computedStyleSupport&&(this.innerHtmlSupport||!this.khtml&&this.xhtmlSupport)};this.ua=ua;var _72=new function(){var _73={leading:true,"margin-left":true,"margin-right":true,"text-indent":true};var _74=" ";function capitalize($){return $.toUpperCase()}this.normalize=function(str){if(_3b.preserveSingleWhitespace){return str.replace(/\s/g,_74)}return str.replace(/(\s)\s+/g,"$1").replace(/\xA0/,_74)};this.textTransform=function(_77,str){switch(_77){case "uppercase":str=str.toUpperCase();break;case "lowercase":str=str.toLowerCase();break;case "capitalize":var _79=str;str=str.replace(/^\w|\s\w/g,capitalize);if(str.indexOf("function capitalize")!=-1){var _7a=_79.replace(/(^|\s)(\w)/g,"$1$1$2$2").split(/^\w|\s\w/g);str="";for(var i=0;i<_7a.length;i++){str+=_7a[i].charAt(0).toUpperCase()+_7a[i].substring(1)}}break}return str};this.toHexString=function(str){if(typeof (str)!="string"||!str.charAt(0)=="#"||str.length!=4&&str.length!=7){return str}str=str.replace(/#/,"");if(str.length==3){str=str.replace(/(.)(.)(.)/,"$1$1$2$2$3$3")}return "0x"+str};this.toJson=function(obj){var _7e="";switch(typeof (obj)){case "string":_7e="\""+obj+"\"";break;case "number":case "boolean":_7e=obj.toString();break;case "object":_7e=[];for(var _7f in obj){if(obj[_7f]==Object.prototype[_7f]){continue}_7e.push("\""+_7f+"\":"+_72.toJson(obj[_7f]))}_7e="{"+_7e.join(",")+"}";break}return _7e};this.convertCssArg=function(arg){if(!arg){return {}}if(typeof (arg)=="object"){if(arg.constructor==Array){arg=arg.join("")}else{return arg}}var obj={};var _82=arg.split("}");for(var i=0;i<_82.length;i++){var $=_82[i].match(/([^\s{]+)\s*\{(.+)\s*;?\s*/);if(!$||$.length!=3){continue}if(!obj[$[1]]){obj[$[1]]={}}var _85=$[2].split(";");for(var j=0;j<_85.length;j++){var $2=_85[j].match(/\s*([^:\s]+)\s*\:\s*([^\s;]+)/);if(!$2||$2.length!=3){continue}obj[$[1]][$2[1]]=$2[2]}}return obj};this.extractFromCss=function(css,_89,_8a,_8b){var _8c=null;if(css&&css[_89]&&css[_89][_8a]){_8c=css[_89][_8a];if(_8b){delete css[_89][_8a]}}return _8c};this.cssToString=function(arg){var css=[];for(var _8f in arg){var _90=arg[_8f];if(_90==Object.prototype[_8f]){continue}css.push(_8f,"{");for(var _91 in _90){if(_90[_91]==Object.prototype[_91]){continue}var _92=_90[_91];if(_73[_91]){_92=parseInt(_92,10)}css.push(_91,":",_92,";")}css.push("}")}return escape(css.join(""))};this.bind=function(_93,_94){return function(){_93[_94].apply(_93,arguments)}}};this.util=_72;var _66={};_66.fragmentIdentifier=new function(){this.fix=true;var _95;this.cache=function(){_95=document.title};function doFix(){document.title=_95}this.restore=function(){if(this.fix){setTimeout(doFix,0)}}};_66.synchronizer=new function(){this.isBlocked=false;this.block=function(){this.isBlocked=true};this.unblock=function(){this.isBlocked=false;_96.replaceAll()}};_66.zoom=new function(){var _97=100;this.getLatest=function(){return _97};if(ua.zoomSupport&&ua.opera){var _98=document.createElement("div");_98.style.position="fixed";_98.style.left="-65536px";_98.style.top="0";_98.style.height="100%";_98.style.width="1px";_98.style.zIndex="-32";document.documentElement.appendChild(_98);function updateZoom(){if(!_98){return}var _99=window.innerHeight/_98.offsetHeight;var _9a=Math.round(_99*100)%10;if(_9a>5){_99=Math.round(_99*100)+10-_9a}else{_99=Math.round(_99*100)-_9a}_97=isNaN(_99)?100:_99;_66.synchronizer.unblock();document.documentElement.removeChild(_98);_98=null}_66.synchronizer.block();setTimeout(updateZoom,54)}};this.hacks=_66;var _9b={kwargs:[],replaceAll:function(){for(var i=0;i<this.kwargs.length;i++){_3b.replace(this.kwargs[i])}this.kwargs=[]}};var _96={kwargs:[],replaceAll:_9b.replaceAll};function isValidDomain(){if(_3b.domains.length==0){return true}var _9d="";try{_9d=document.domain}catch(e){}if(_3b.fromLocal&&sIFR.domains[0]!="localhost"){sIFR.domains.unshift("localhost")}for(var i=0;i<_3b.domains.length;i++){var _9f=_3b.domains[i];if(_9f=="*"||_9f==_9d){return true}var _a0=_9f.lastIndexOf("*");if(_a0>-1){_9f=_9f.substr(_a0+1);var _a1=_9d.lastIndexOf(_9f);if(_a1>-1&&(_a1+_9f.length)==_9d.length){return true}}}return false}this.activate=function(){if(!ua.supported||!this.isEnabled||this.isActive||!isValidDomain()){return}if(arguments.length>0){this.prefetch.apply(this,arguments)}this.isActive=true;if(this.hideElements){this.setFlashClass()}if(ua.ieWin&&_66.fragmentIdentifier.fix&&window.location.hash!=""){_66.fragmentIdentifier.cache()}else{_66.fragmentIdentifier.fix=false}if(!this.registerEvents){return}function handler(evt){_3b.initialize();if(evt&&evt.type=="load"){if(document.removeEventListener){document.removeEventListener("DOMContentLoaded",handler,false)}if(window.removeEventListener){window.removeEventListener("load",handler,false)}}}if(window.addEventListener){if(_3b.useDomContentLoaded&&ua.gecko){document.addEventListener("DOMContentLoaded",handler,false)}window.addEventListener("load",handler,false)}else{if(ua.ieWin){if(_3b.useDomContentLoaded){document.write("<scr"+"ipt id=__sifr_ie_onload defer src=//:></script>");document.getElementById("__sifr_ie_onload").onreadystatechange=function(){if(this.readyState=="complete"){handler();this.removeNode()}}}window.attachEvent("onload",handler)}}};this.setFlashClass=function(){if(this.hasFlashClassSet){return}dom.addClass(_3c,dom.getBody()||document.documentElement);this.hasFlashClassSet=true};this.removeFlashClass=function(){if(!this.hasFlashClassSet){return}dom.removeClass(_3c,dom.getBody());dom.removeClass(_3c,document.documentElement);this.hasFlashClassSet=false};this.initialize=function(){if(_4c||!this.isActive||!this.isEnabled){return}_4c=true;_9b.replaceAll();clearPrefetch()};function getSource(src){if(typeof (src)!="string"){if(src.src){src=src.src}if(typeof (src)!="string"){var _a4=[];for(var _a5 in src){if(src[_a5]!=Object.prototype[_a5]){_a4.push(_a5)}}_a4.sort().reverse();var _a6="";var i=-1;while(!_a6&&++i<_a4.length){if(parseFloat(_a4[i])<=ua.flashVersion){_a6=src[_a4[i]]}}src=_a6}}if(!src&&_3b.debugMode){throw new Error("sIFR: Could not determine appropriate source")}if(ua.ie&&src.charAt(0)=="/"){src=window.location.toString().replace(/([^:]+)(:\/?\/?)([^\/]+).*/,"$1$2$3")+src}return src}this.prefetch=function(){if(!ua.requiresPrefetch||!ua.supported||!this.isEnabled||!isValidDomain()){return}if(this.setPrefetchCookie&&new RegExp(";?"+_47+"=true;?").test(document.cookie)){return}try{_4b=true;if(ua.ieWin){prefetchIexplore(arguments)}else{prefetchLight(arguments)}if(this.setPrefetchCookie){document.cookie=_47+"=true;path="+this.cookiePath}}catch(e){if(_3b.debugMode){throw e}}};function prefetchIexplore(_a8){for(var i=0;i<_a8.length;i++){document.write("<script defer type=\"sifr/prefetch\" src=\""+getSource(_a8[i])+"\"></script>")}}function prefetchLight(_aa){for(var i=0;i<_aa.length;i++){new Image().src=getSource(_aa[i])}}function clearPrefetch(){if(!ua.ieWin||!_4b){return}try{var _ac=document.getElementsByTagName("script");for(var i=_ac.length-1;i>=0;i--){var _ae=_ac[i];if(_ae.type=="sifr/prefetch"){_ae.parentNode.removeChild(_ae)}}}catch(e){}}function getRatio(_af,_b0){for(var i=0;i<_b0.length;i+=2){if(_af<=_b0[i]){return _b0[i+1]}}return _b0[_b0.length-1]}function getFilters(obj){var _b3=[];for(var _b4 in obj){if(obj[_b4]==Object.prototype[_b4]){continue}var _b5=obj[_b4];_b4=[_b4.replace(/filter/i,"")+"Filter"];for(var _b6 in _b5){if(_b5[_b6]==Object.prototype[_b6]){continue}_b4.push(_b6+":"+escape(_72.toJson(_72.toHexString(_b5[_b6]))))}_b3.push(_b4.join(","))}return _b3.join(";")}function calculate(_b7){var _b8,_b9;if(!ua.ie){_b8=dom.getStyleAsInt(_b7,"lineHeight");_b9=Math.floor(dom.getStyleAsInt(_b7,"height")/_b8)}else{if(ua.ie){var _ba=_b7.innerHTML;_b7.style.visibility="visible";_b7.style.overflow="visible";_b7.style.position="static";_b7.style.zoom="normal";_b7.style.writingMode="lr-tb";_b7.style.width=_b7.style.height="auto";_b7.style.maxWidth=_b7.style.maxHeight=_b7.style.styleFloat="none";var _bb=_b7;var _bc=_b7.currentStyle.hasLayout;if(_bc){dom.setInnerHtml(_b7,"<div class=\""+_43+"\">X<br />X<br />X</div>");_bb=_b7.firstChild}else{dom.setInnerHtml(_b7,"X<br />X<br />X")}var _bd=_bb.getClientRects();_b8=_bd[1].bottom-_bd[1].top;_b8=Math.ceil(_b8*0.8);if(_bc){dom.setInnerHtml(_b7,"<div class=\""+_43+"\">"+_ba+"</div>");_bb=_b7.firstChild}else{dom.setInnerHtml(_b7,_ba)}_bd=_bb.getClientRects();_b9=_bd.length;if(_bc){dom.setInnerHtml(_b7,_ba)}_b7.style.visibility=_b7.style.width=_b7.style.height=_b7.style.maxWidth=_b7.style.maxHeight=_b7.style.overflow=_b7.style.styleFloat=_b7.style.position=_b7.style.zoom=_b7.style.writingMode=""}}return {lineHeight:_b8,lines:_b9}}this.replace=function(_be,_bf){if(!ua.supported){return}if(_bf){for(var _c0 in _be){if(typeof (_bf[_c0])=="undefined"){_bf[_c0]=_be[_c0]}}_be=_bf}if(!_4c){return _9b.kwargs.push(_be)}if(_66.synchronizer.isBlocked){return _96.kwargs.push(_be)}var _c1=_be.elements;if(!_c1&&parseSelector){_c1=parseSelector(_be.selector)}if(_c1.length==0){return}this.setFlashClass();var src=getSource(_be.src);var css=_72.convertCssArg(_be.css);var _c4=getFilters(_be.filters);var _c5=(_be.forceClear==null)?_3b.forceClear:_be.forceClear;var _c6=(_be.fitExactly==null)?_3b.fitExactly:_be.fitExactly;var _c7=_c6||(_be.forceWidth==null?_3b.forceWidth:_be.forceWidth);var _c8=parseInt(_72.extractFromCss(css,".sIFR-root","leading"))||0;var _c9=_72.extractFromCss(css,".sIFR-root","font-size",true)||0;var _ca=_72.extractFromCss(css,".sIFR-root","background-color",true)||"#FFFFFF";var _cb=_72.extractFromCss(css,".sIFR-root","kerning",true)||"";var _cc=_be.gridFitType||_72.extractFromCss(css,".sIFR-root","text-align")=="right"?"subpixel":"pixel";var _cd=_3b.forceTextTransform?_72.extractFromCss(css,".sIFR-root","text-transform",true)||"none":"none";var _ce=_72.extractFromCss(css,".sIFR-root","opacity",true)||"100";var _cf=_be.pixelFont||false;var _d0=_be.ratios||_48;if(parseInt(_c9).toString()!=_c9&&_c9.indexOf("px")==-1){_c9=0}else{_c9=parseInt(_c9)}if(parseFloat(_ce)<1){_ce=100*parseFloat(_ce)}var _d1=null;var _d2="";if(_c6){_72.extractFromCss(css,".sIFR-root","text-align",true)}if(!_be.modifyCss){_d2=_72.cssToString(css);_d1=_3b.fixHover&&_d2.indexOf("%3Ahover")>-1}var _d3=!ua.opera&&_3b.delayCss;var _d4=_be.wmode||"";if(!_d4){if(_be.transparent){_d4="transparent"}else{if(_be.opaque){_d4="opaque"}}}if(_d4=="transparent"){if(!ua.transparencySupport){_d4="opaque"}else{_ca="transparent"}}for(var i=0;i<_c1.length;i++){var _d6=_c1[i];if(!ua.verifiedKonqueror){if(dom.getComputedStyle(_d6,"lineHeight").match(/e\+08px/)){ua.supported=_3b.isEnabled=false;this.removeFlashClass();return}ua.verifiedKonqueror=true}if(dom.hasOneOfClassses([_3d,_3e,_40,_41],_d6)){continue}var _d7=_d6.offsetHeight;var _d8=_d6.offsetWidth;var _d9=dom.getComputedStyle(_d6,"display");if(!_d7||!_d8||_d9==null||_d9=="none"){continue}if(_c5&&ua.gecko){_d6.style.clear="both"}var _da=null;if(_3b.fixWrap&&ua.ie&&_d9=="block"){_da=_d6.innerHTML;dom.setInnerHtml(_d6,"X")}_d8=dom.getStyleAsInt(_d6,"width",ua.ie);if(_d8==0){var _db=dom.getStyleAsInt(_d6,"paddingRight",true);var _dc=dom.getStyleAsInt(_d6,"paddingLeft",true);var _dd=dom.getStyleAsInt(_d6,"borderRightWidth",true);var _de=dom.getStyleAsInt(_d6,"borderLeftWidth",true);_d8=_d6.offsetWidth-_dc-_db-_de-_dd}if(_da&&_3b.fixWrap&&ua.ie){dom.setInnerHtml(_d6,_da)}var _df,_e0;if(!_c9){var _e1=calculate(_d6);_df=Math.min(_45,Math.max(_44,_e1.lineHeight));if(_cf){_df=Math.max(8,8*Math.round(_df/8))}_e0=_e1.lines;if(isNaN(_e0)||!isFinite(_e0)||_e0==0){_e0=1}if(_e0>1&&_c8){_d7+=Math.round((_e0-1)*_c8)}}else{_df=_c9;_e0=1}_d7=Math.round(_e0*_df);if(_c5&&ua.gecko){_d6.style.clear=""}var _e2=dom.create("span");_e2.className=_41;var _e3=_d6.cloneNode(true);for(var j=0,l=_e3.childNodes.length;j<l;j++){_e2.appendChild(_e3.childNodes[j].cloneNode(true))}if(_be.modifyContent){_be.modifyContent(_e3,_be.selector)}if(_be.modifyCss){_d2=_be.modifyCss(css,_e3,_be.selector)}if(_d1==null){_d1=_3b.fixHover&&_d2.indexOf("%3Ahover")>-1}var _e6=handleContent(_e3,_cd);if(_be.modifyContentString){_e6=_be.modifyContentString(_e6,_be.selector)}if(_e6==""){continue}var _e7=["content="+_e6,"width="+_d8,"height="+_d7,"fitexactly="+(_c6?"true":""),"tunewidth="+(_be.tuneWidth||""),"tuneheight="+(_be.tuneHeight||""),"offsetleft="+(_be.offsetLeft||""),"offsettop="+(_be.offsetTop||""),"thickness="+(_be.thickness||""),"sharpness="+(_be.sharpness||""),"kerning="+_cb,"gridfittype="+_cc,"zoomsupport="+ua.zoomSupport,"flashfilters="+_c4,"opacity="+_ce,"blendmode="+(_be.blendMode||""),"size="+_df,"zoom="+dom.getZoom(),"css="+_d2,"selectable="+(_be.selectable==null?"true":_be.selectable),"lines="+_e0];var _e8=encodeURI(_e7.join("&"));var _e9="sIFR_callback_"+_4a++;var _ea=new CallbackInfo(_e9,_e7,_be.onReplacement,_d1);window[_e9+"_DoFSCommand"]=(function(_eb){return function(_ec,arg){_eb.handle(_ec,arg)}})(_ea);_d7=Math.round(_e0*getRatio(_df,_d0)*_df)+_49;var _ee=_c7?_d8:"100%";var _ef;if(ua.ie){_ef=["<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" id=\"",_e9,"\" sifr=\"true\" width=\"",_ee,"\" height=\"",_d7,"\" class=\"",_3f,"\">","<param name=\"movie\" value=\"",src,"\"></param>","<param name=\"flashvars\" value=\"",_e8,"\"></param>","<param name=\"allowScriptAccess\" value=\"always\"></param>","<param name=\"quality\" value=\"best\"></param>","<param name=\"wmode\" value=\"",_d4,"\"></param>","<param name=\"bgcolor\" value=\"",_ca,"\"></param>","<param name=\"name\" value=\"",_e9,"\"></param>","</object>","<scr","ipt event=FSCommand(info,args) for=",_e9,">",_e9,"_DoFSCommand(info, args);","</","script>"].join("")}else{_ef=["<embed type=\"application/x-shockwave-flash\"",(_d3?" class=\""+_3f+"\"":"")," src=\"",src,"\" quality=\"best\" flashvars=\"",_e8,"\" width=\"",_ee,"\" height=\"",_d7,"\" wmode=\"",_d4,"\" bgcolor=\"",_ca,"\" name=\"",_e9,"\" id=\"",_e9,"\" allowScriptAccess=\"always\" sifr=\"true\"></embed>"].join("")}dom.setInnerHtml(_d6,_ef);_ea.flashNode=_d6.firstChild;_ea.html=_ef;_3b.callbacks.push(_ea);if(_be.selector){if(!_3b.callbacks[_be.selector]){_3b.callbacks[_be.selector]=[_ea]}else{_3b.callbacks[_be.selector].push(_ea)}}_d6.appendChild(_e2);dom.addClass(_d3?_3e:_3d,_d6);_ea.setupFixHover()}_66.fragmentIdentifier.restore()};this.getCallbackByFlashElement=function(_f0){for(var i=0;i<_3b.callbacks.length;i++){if(_3b.callbacks[i].id==_f0.getAttribute("id")){return _3b.callbacks[i]}}};function handleContent(_f2,_f3){var _f4=[],_f5=[];var _f6=_f2.childNodes;var i=0;while(i<_f6.length){var _f8=_f6[i];if(_f8.nodeType==3){var _f9=_72.normalize(_f8.nodeValue);_f9=_72.textTransform(_f3,_f9);_f5.push(_f9.replace(/\%/g,"%25").replace(/\&/g,"%26").replace(/\,/g,"%2C").replace(/\+/g,"%2B"))}if(_f8.nodeType==1){var _fa=[];var _fb=_f8.nodeName.toLowerCase();var _fc=_f8.className||"";if(/\s+/.test(_fc)){if(_fc.indexOf(_42)>-1){_fc=_fc.match("(\\s|^)"+_42+"-([^\\s$]*)(\\s|$)")[2]}else{_fc=_fc.match(/^([^\s]+)/)[1]}}if(_fc!=""){_fa.push("class=\""+_fc+"\"")}if(_fb=="a"){var _fd=_f8.getAttribute("href")||"";var _fe=_f8.getAttribute("target")||"";_fa.push("href=\""+_fd+"\"","target=\""+_fe+"\"")}_f5.push("<"+_fb+(_fa.length>0?" ":"")+escape(_fa.join(" "))+">");if(_f8.hasChildNodes()){_f4.push(i);i=0;_f6=_f8.childNodes;continue}else{if(!/^(br|img)$/i.test(_f8.nodeName)){_f5.push("</",_f8.nodeName.toLowerCase(),">")}}}if(_f4.length>0&&!_f8.nextSibling){do{i=_f4.pop();_f6=_f8.parentNode.parentNode.childNodes;_f8=_f6[i];if(_f8){_f5.push("</",_f8.nodeName.toLowerCase(),">")}}while(i==_f6.length-1&&_f4.length>0)}i++}return _f5.join("").replace(/\n|\r/g,"")}function CallbackInfo(id,vars,_101,_102){this.id=id;this.vars=vars;this._replacementHandler=_101;this._firedReplacementEvent=!(this._replacementHandler!=null);this._fixHover=_102;this._setClasses=!_3b.delayCss;this.html="";this._pings=0}CallbackInfo.prototype.getFlashElement=function(){return document.getElementById(this.id)};CallbackInfo.prototype.handle=function(info,arg){if(/(FSCommand\:)?resize/.test(info)){var _105=this.getFlashElement();var $=arg.split(/\:|,/);_105.setAttribute($[0],$[1]);if($.length>2){_105.setAttribute($[2],$[3])}if(!this._setClasses){if(!ua.ie&&!ua.opera){dom.addClass(_3f,_105)}dom.removeClass(_3e,_105.parentNode);dom.addClass(_3d,_105.parentNode);this._setClasses=true}if(ua.khtml){var _107=_105.offsetHeight}if(!this._firedReplacementEvent){this._replacementHandler(this);this._firedReplacementEvent=true}}else{if(/(FSCommand\:)?resetmovie/.test(info)){this.resetMovie()}else{if(/(FSCommand\:)?ping/.test(info)){if(this._pings>0){this.setupFixHover()}this._pings++}else{if(this.debugHandler&&/(FSCommand\:)?debug/.test(info)){this.debugHandler(info,arg)}}}}};CallbackInfo.prototype.call=function(type,_109){var _10a=this.getFlashElement();if(!_10a){return}_10a.SetVariable("callbackType",type);_10a.SetVariable("callbackValue",_109);_10a.SetVariable("callbackTrigger",true)};CallbackInfo.prototype.replaceText=function(_10b){_10b=escape(_10b);this.call("replacetext",_10b);this.vars[0]="content="+_10b;this.html=this.html.replace(/(flashvars(=|\"\svalue=)\")[^\"]+/,"$1"+encodeURI(this.vars.join("&")))};CallbackInfo.prototype.resetMovie=function(){var _10c=this.getFlashElement();var node=_10c.parentNode;node.replaceChild(dom.nodeFromHtml(this.html),_10c);this.setupFixHover()};CallbackInfo.prototype.setupFixHover=function(){var _10e=this.getFlashElement();if(!this._fixHover||!_10e){return}var node=_10e.parentNode;if(node.addEventListener){node.addEventListener("mouseout",_72.bind(this,"fixHover"),false)}else{if(node.attachEvent){node.attachEvent("onmouseout",_72.bind(this,"fixHover"))}}};CallbackInfo.prototype.fixHover=function(){this.call("resettext")}}; \ No newline at end of file diff --git a/library/simplepie/demo/for_the_demo/simplepie.css b/library/simplepie/demo/for_the_demo/simplepie.css deleted file mode 100644 index 3753cb96de..0000000000 --- a/library/simplepie/demo/for_the_demo/simplepie.css +++ /dev/null @@ -1,397 +0,0 @@ -/* -Theme Name: SimplePie -Theme URI: http://simplepie.org -Description: A simple, yet beautiful theme inspired by several cleanly designed websites. -Version: 1.4 -Author: Ryan Parman -Author URI: http://skyzyx.com -Updated: 21 June 2007 -*/ - - -/********************************************* -HYPERLINK STYLES -*********************************************/ -a { - color:#369; - text-decoration:underline; - padding:0 1px; -} - -a:hover { - color:#fff !important; - background-color:#333; - text-decoration:none; - padding:0 1px; -} - -a.nohover { - text-decoration:none; - border:none; -} - -a.nohover:hover { - background-color:transparent; - border:none; -} - -a.namelink { - padding:0; - margin:0; - overflow:hidden; - height:1px; -} - -h4 a, -.sample_feeds a { - color:#000; -} - - -/********************************************* -GENERAL STYLES -*********************************************/ -body { - /*font:12px/18px Verdana, sans-serif;*/ - font:14px/1.5em "Lucida Grande", Tahoma, sans-serif; - letter-spacing:0px; - color:#333; - background-color:#fff; - margin:0; - padding:0; -} - -div#site { - width:600px; - margin:50px auto 0 auto; -} - -h1#logo { - margin:0; - padding:0; - text-align:center; -} - -h1#logo a, -h1#logo a:hover { - background-color:transparent; - text-decoration:none; - padding:0; -} - -h2.image { - margin:0; - padding:0; - text-align:center; -} - -h3 { - margin:20px 0 0 0; - padding:0; - font-size:1.5em; -} - -h4 { - margin:20px 0 0 0; - padding:0; - font-size:1.2em; - letter-spacing:-1px; -} - -h5 { - margin:10px 0 0 0; - padding:0; - font-size:1em; - font-weight:bold; -} - -em { - font-style:normal; - background-color:#ffc; -} - -p { - margin:0; - padding:5px 0; -} - -ul, ol { - margin:10px 0 10px 20px; - padding:0 0 0 15px; -} - -ul li, ol li { - margin:0 0 7px 0; - padding:0 0 0 3px; -} - -form { - margin:0; - padding:0; -} - -code { - font-size:1em; - background-color:#f3f3ff; - color:#000; -} - -div#site pre { - background-color:#f3f3ff; - color:#000080; - border:1px dotted #000080; - overflow:auto; - padding:3px 5px; -} - -blockquote { - font-size:1em; - color:#666; - border-left:4px solid #666; - margin:10px 0 10px 30px; - padding:0 5px 0 10px; - background:#f3f3f3 url(background_blockquote.png) repeat top left; -} - -input, select, textarea { - font-size:12px; - line-height:1.2em; - padding:2px; -} - -input[type=text], select, textarea { - background-color:#e9f5ff; - border:1px solid #333; -} - -input[type=text]:focus, select:focus, textarea:focus { - background-color:#ffe; -} - -.clearLeft {clear:left;} -.clearRight {clear:right;} -.clearBoth {clear:both;} -.hide {display:none;} - - -/********************************************* -NAVIGATION STYLES -*********************************************/ -div#header { - background:#fff url(top_gradient.gif) repeat-x top left; - margin:0; - padding:0; -} - -div#header form { - margin:0; - padding:0; -} - -div#header div#headerInner { - margin:0; - padding:0; -} - -div#header div#headerInner div#logoContainer {} - -div#header div#headerInner div#logoContainerInner { - width:550px; - margin:0 auto; - padding:20px; -} - -div#header div#headerInner div#logoContainer div#logo { - float:left; - width:200px; -} - -div#header div#headerInner div#logoContainer div#logo a, -div#header div#headerInner div#logoContainer div#logo a:hover { - border:none; - background:none; -} - -div#header div#headerInner div#logoContainer div#feed { - float:right; - width:300px; - text-align:right; - padding:10px 0 0 0; -} - -div#header div#headerInner div#logoContainer div#feed input.text { - width:60%; -} - -div#header div#headerInner div#menu { - background:#eee url(background_menuitem_shadow.gif) repeat-x top left; - border-top:2px solid #ccc; - border-bottom:1px solid #ddd; - text-align:center; -} - -div#header div#headerInner div#menu table { - width:auto; - margin:0 auto; -} - -div#header div#headerInner div#menu ul { - display:block; - width:100%; - margin:0 auto; - padding:0; - font-size:12px; -} - -div#header div#headerInner div#menu ul li { - display:block; - float:left; -} - -div#header div#headerInner div#menu ul li a { - display:block; - margin:-2px 0 0 0; - padding:5px 7px 8px 7px; - text-decoration:none; - color:#666 !important; - background-color:transparent; -} - -div#header div#headerInner div#menu ul li a:hover { - display:block; - margin:-2px 0 0 0; - padding:5px 7px 8px 7px; - text-decoration:none; - color:#666; - background:#fff url(background_menuitem_off.gif) no-repeat bottom right; -} - -body#bodydemo div#header div#headerInner div#menu ul li#demo a { - display:block; - margin:-2px 0 0 0; - padding:5px 7px 8px 7px; - text-decoration:none; - color:#333; - font-weight:bold; - background:#fff url(background_menuitem.gif) no-repeat bottom right; -} - - -/********************************************* -CONTENT STYLES -*********************************************/ -div.chunk { - margin:20px 0 0 0; - padding:0 0 10px 0; - border-bottom:1px solid #ccc; -} - -div.topchunk { - margin:0 !important; -} - -.footnote, -.footnote a { - font-size:12px; - line-height:1.3em; - color:#aaa; -} - -.footnote em { - background-color:transparent; - font-style:italic; -} - -.footnote code { - background-color:transparent; - font:11px/14px monospace; - color:#aaa; -} - -p.subscribe { - background-color:#f3f3f3; - font-size:12px; - text-align:center; -} - -p.highlight { - background-color:#ffc; - font-size:12px; - text-align:center; -} - -p.sample_feeds { - font-size:12px; - line-height:1.2em; -} - -div.sp_errors { - background-color:#eee; - padding:5px; - text-align:center; - font-size:12px; -} - -.noborder { - border:none !important; -} - -img.favicon { - margin:0 4px -2px 0; - width:16px; - height:16px; -} - -p.favicons a, -p.favicons a:hover { - border:none; - background-color:transparent; -} - -p.favicons img { - border:none; -} - - -/********************************************* -DEMO STYLES -*********************************************/ -div#sp_input { - background-color:#ffc; - border:2px solid #f90; - padding:5px; - text-align:center; -} - -div#sp_input input.text { - border:1px solid #999; - background:#e9f5ff url(feed.png) no-repeat 4px 50%; - width:75%; - padding:2px 2px 2px 28px; - font:18px/22px "Lucida Grande", Verdana, sans-serif; - font-weight:bold; - letter-spacing:-1px; -} - -form#sp_form { - margin:15px 0; -} - -div.focus { - margin:0; - padding:10px 20px; - background-color:#efe; -} - -p.sample_feeds { - text-align:justify; -} - - -/********************************************* -SIFR STYLES -*********************************************/ -.sIFR-active h3.header { - visibility:hidden; - line-height:1em; -} diff --git a/library/simplepie/demo/for_the_demo/sleight.js b/library/simplepie/demo/for_the_demo/sleight.js deleted file mode 100644 index 4b5058e9a6..0000000000 --- a/library/simplepie/demo/for_the_demo/sleight.js +++ /dev/null @@ -1,31 +0,0 @@ -/********************************************************** -Sleight -(c) 2001, Aaron Boodman -http://www.youngpup.net -**********************************************************/ - -if (navigator.platform == "Win32" && navigator.appName == "Microsoft Internet Explorer" && window.attachEvent) -{ - document.writeln('<style type="text/css">img { visibility:hidden; } </style>'); - window.attachEvent("onload", fnLoadPngs); -} - -function fnLoadPngs() -{ - var rslt = navigator.appVersion.match(/MSIE (\d+\.\d+)/, ''); - var itsAllGood = (rslt != null && Number(rslt[1]) >= 5.5); - - for (var i = document.images.length - 1, img = null; (img = document.images[i]); i--) - { - if (itsAllGood && img.src.match(/\.png$/i) != null) - { - var src = img.src; - var div = document.createElement("DIV"); - div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizing='scale')" - div.style.width = img.width + "px"; - div.style.height = img.height + "px"; - img.replaceNode(div); - } - img.style.visibility = "visible"; - } -} diff --git a/library/simplepie/demo/for_the_demo/source_files/place_audio_fireworksfile.png b/library/simplepie/demo/for_the_demo/source_files/place_audio_fireworksfile.png deleted file mode 100644 index 2bfd87d0ce14c424fd3b72377b085bb0b1510bd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39177 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 diff --git a/library/simplepie/demo/for_the_demo/source_files/place_video_fireworksfile.png b/library/simplepie/demo/for_the_demo/source_files/place_video_fireworksfile.png deleted file mode 100644 index d0629769cfec742664ec0b739d93ebf4418d15e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115826 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�yvwvr{%euJem^4Qq_AqbXR}PnKcoLJNe#h#<$N7 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 Y{xKQOm;9p6g#iC3$f!!!Nt%cL4}oKrIsgCw diff --git a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/SifrStyleSheet.as b/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/SifrStyleSheet.as deleted file mode 100644 index 6a98ca5522..0000000000 --- a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/SifrStyleSheet.as +++ /dev/null @@ -1,71 +0,0 @@ -/*=:project - scalable Inman Flash Replacement (sIFR) version 3. - - =:file - Copyright: 2006 Mark Wubben. - Author: Mark Wubben, <http://novemberborn.net/> - - =:history - * IFR: Shaun Inman - * sIFR 1: Mike Davidson, Shaun Inman and Tomas Jogin - * sIFR 2: Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben - - =:license - This software is licensed and provided under the CC-GNU LGPL. - See <http://creativecommons.org/licenses/LGPL/2.1/> -*/ - -import TextField.StyleSheet; - -class SifrStyleSheet extends TextField.StyleSheet { - public var fontSize; - public var latestLeading = 0; - - public function parseCSS(cssText:String) { - var native = new TextField.StyleSheet(); - var parsed = native.parseCSS(cssText); - - if(!parsed) return false; - - var selectors = native.getStyleNames(); - for(var i = selectors.length - 1; i >= 0; i--) { - var selector = selectors[i]; - var nativeStyle = native.getStyle(selector); - var style = this.getStyle(selector) || nativeStyle; - if(style != nativeStyle) { - for(var property in nativeStyle) style[property] = nativeStyle[property]; - } - this.setStyle(selector, style); - } - - return true; - } - - // Apply leading to the textFormat. Much thanks to <http://www.blog.lessrain.com/?p=98>. - private function applyLeading(format, leading) { - this.latestLeading = leading; - - if(leading >= 0) { - format.leading = leading; - return format; - } - - // Workaround for negative leading, which is ignored otherwise. - var newFormat = new TextFormat(null, null, null, null, null, null, null, null, null, null, null, null, leading); - for(var property in format) if(property != 'leading') newFormat[property] = format[property]; - - return newFormat; - } - - public function transform(style) { - var format = super.transform(style); - if(style.leading) format = applyLeading(format, style.leading); - if(style.letterSpacing) format.letterSpacing = style.letterSpacing; - // Support font sizes relative to the size of .sIFR-root. - if(this.fontSize && style.fontSize && style.fontSize.indexOf('%')) { - format.size = this.fontSize * parseInt(style.fontSize) / 100; - } - format.kerning = _root.kerning == 'true' || !(_root.kerning == 'false') || sIFR.defaultKerning; - return format; - } -} \ No newline at end of file diff --git a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/_README_.txt b/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/_README_.txt deleted file mode 100644 index 2b9d32d202..0000000000 --- a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/_README_.txt +++ /dev/null @@ -1,12 +0,0 @@ -This is a pre-release nightly of sIFR 3 (r245 to be exact). We (the SimplePie team) will be updating the -sIFR code and font files from time to time as new releases of sIFR 3 are made available. - -In this folder you'll find a few Flash 8 files. The only one of you might want to mess with is sifr.fla. - * Open it up - * Double-click the rectangle in the middle - * Select all - * Change the font - -More information about sIFR 3 can be found here: - * http://dev.novemberborn.net/sifr3/ - * http://wiki.novemberborn.net/sifr3/ \ No newline at end of file diff --git a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/options.as b/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/options.as deleted file mode 100644 index 4d371954bc..0000000000 --- a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/options.as +++ /dev/null @@ -1,12 +0,0 @@ -// MTASC only parses as-files with class definitions, so here goes... -class Options { - public static function apply() { - sIFR.fromLocal = true; - sIFR.domains = ['*']; - - // Parsing `p.foo` might not work, see: <http://livedocs.macromedia.com/flash/mx2004/main_7_2/wwhelp/wwhimpl/common/html/wwhelp.htm?context=Flash_MX_2004&file=00001766.html> - // Appearantly you have to use hex color codes as well, names are not supported! - - sIFR.styles.parseCSS('.foo { text-decoration: underline; }'); - } -} diff --git a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/sIFR.as b/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/sIFR.as deleted file mode 100644 index 4902e003f3..0000000000 --- a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/sIFR.as +++ /dev/null @@ -1,359 +0,0 @@ -/*=:project - scalable Inman Flash Replacement (sIFR) version 3. - - =:file - Copyright: 2006 Mark Wubben. - Author: Mark Wubben, <http://novemberborn.net/> - - =:history - * IFR: Shaun Inman - * sIFR 1: Mike Davidson, Shaun Inman and Tomas Jogin - * sIFR 2: Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben - - =:license - This software is licensed and provided under the CC-GNU LGPL. - See <http://creativecommons.org/licenses/LGPL/2.1/> -*/ - -import SifrStyleSheet; - -class sIFR { - public static var DEFAULT_TEXT = 'Rendered with sIFR 3, revision 245'; - public static var CSS_ROOT_CLASS = 'sIFR-root'; - public static var DEFAULT_WIDTH = 300; - public static var DEFAULT_HEIGHT = 100; - public static var DEFAULT_ANTI_ALIAS_TYPE = 'advanced'; - public static var MARGIN_LEFT = -3; - public static var PADDING_BOTTOM = 5; // Extra padding to make sure the movie is high enough in most cases. - public static var LEADING_REMAINDER = 2; // Flash uses the specified leading minus 2 as the applied leading. - - public static var MAX_FONT_SIZE = 126; - public static var ALIASING_MAX_FONT_SIZE = 48; - - //= Holds CSS properties and other rendering properties for the Flash movie. - // *Don't overwrite!* - public static var styles:SifrStyleSheet = new SifrStyleSheet(); - //= Allow sIFR to be run from localhost - public static var fromLocal:Boolean = true; - //= Array containing domains for which sIFR may render text. Used to prevent - // hotlinking. Use `*` to allow all domains. - public static var domains:Array = []; - //= Whether kerning is enabled by default. This can be overriden from the client side. - // See also <http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002811.html>. - public static var defaultKerning:Boolean = true; - //= Default value which can be overriden from the client side. - // See also <http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002788.html>. - public static var defaultSharpness:Number = 0; - //= Default value which can be overriden from the client side. - // See also <http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002787.html>. - public static var defaultThickness:Number = 0; - //= Default value which can be overriden from the client side. - // See also <http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002732.html>. - public static var defaultOpacity:Number = -1; // Use client settings - //= Default value which can be overriden from the client side. - // See also <http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002788.html>. - public static var defaultBlendMode:Number = -1; // Use cliest settings - //= Overrides the grid fit type as defined on the client side. - // See also <http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002444.html>. - public static var enforcedGridFitType:String = null; - //= If `true` sIFR won't override the anti aliasing set in the Flash IDE when exporting. - // Thickness and sharpness won't be affected either. - public static var preserveAntiAlias:Boolean = false; - //= If `true` sIFR will disable anti-aliasing if the font size is larger than `ALIASING_MAX_FONT_SIZE`. - // This setting is *independent* from `preserveAntiAlias`. - public static var conditionalAntiAlias:Boolean = true; - //= Sets the anti alias type. By default it's `DEFAULT_ANTI_ALIAS_TYPE`. - // See also <http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002733.html>. - public static var antiAliasType:String = null; - //= Flash filters can be added to this array and will be applied to the text field. - public static var filters:Array = []; - //= A mapping from the names of the filters to their actual objecs, used when transforming - // filters defined on the client. You can add additional filters here so they'll be supported - // when defined on the client. - public static var filterMap:Object = { - DisplacementMapFilter : flash.filters.DisplacementMapFilter, - ColorMatrixFilter : flash.filters.ColorMatrixFilter, - ConvolutionFilter : flash.filters.ConvolutionFilter, - GradientBevelFilter : flash.filters.GradientBevelFilter, - GradientGlowFilter : flash.filters.GradientGlowFilter, - BevelFilter : flash.filters.BevelFilter, - GlowFilter : flash.filters.GlowFilter, - BlurFilter : flash.filters.BlurFilter, - DropShadowFilter : flash.filters.DropShadowFilter - }; - - private static var instance; - - private var textField; - private var content; - private var realHeight; - private var originalHeight; - private var currentHeight; - private var fontSize; - private var tuneWidth; - private var tuneHeight; - - - - //= Sets the default styles for `sIFR.styles`. This method is called - // directly in `sifr.fla`, before options are applied. - public static function setDefaultStyles() { - sIFR.styles.parseCSS([ - '.', CSS_ROOT_CLASS, ' { color: #000000; }', - 'strong { display: inline; font-weight: bold; } ', - 'em { display: inline; font-style: italic; }', - 'a { color: #0000FF; text-decoration: underline; }', - 'a:hover { color: #0000FF; text-decoration: none; }' - ].join('')); - } - - //= Validates the domain sIFR is being used on. - // Returns `true` if the domain is valid, `false` otherwise. - public static function checkDomain():Boolean { - if(sIFR.domains.length == 0) return true; - - var domain = (new LocalConnection()).domain(); - if(sIFR.fromLocal) sIFR.domains.push('localhost'); - - for(var i = 0; i < sIFR.domains.length; i++) { - var match = sIFR.domains[i]; - if(match == '*' || match == domain) return true; - - var wildcard = match.lastIndexOf('*'); - if(wildcard > -1) { - match = match.substr(wildcard + 1); - var matchPosition = domain.lastIndexOf(match); - if(matchPosition > -1 && (matchPosition + match.length) == domain.length) return true; - } - } - - return false; - } - - //= Runs sIFR. Called automatically. - public static function run() { - var holder = _root.holder; - var content = checkDomain() ? unescape(_root.content) : DEFAULT_TEXT - if(content == 'undefined' || content == '') { - content = DEFAULT_TEXT; - fscommand('resetmovie', ''); - } else fscommand('ping', ''); - - // Sets stage parameters - Stage.scaleMode = 'noscale'; - Stage.align = 'TL'; - Stage.showMenu = false; - - // Other parameters - var opacity = parseInt(_root.opacity); - if(!isNaN(opacity)) holder._alpha = sIFR.defaultOpacity == -1 ? opacity : sIFR.defaultOpacity; - else holder._alpha = 100; - _root.blendMode = sIFR.defaultBlendMode == -1 ? _root.blendmode : sIFR.defaultBlendMode; - - sIFR.instance = new sIFR(holder.txtF, content); - // This should ignore resizes from the callback. Disabled for now. -/* if(_root.zoomsupport == 'true') Stage.addListener({onResize: function() { sIFR.instance.scale() }});*/ - - // Setup callbacks - _root.watch('callbackTrigger', function() { - sIFR.callback(); - return false; - }); - } - - private static function eval(str) { - var as; - - if(str.charAt(0) == '{') { // Ah, we need to create an object - as = {}; - str = str.substring(1, str.length - 1); - var $ = str.split(','); - for(var i = 0; i < $.length; i++) { - var $1 = $[i].split(':'); - as[$1[0]] = sIFR.eval($1[1]); - } - } else if(str.charAt(0) == '"') { // String - as = str.substring(1, str.length - 1); - } else if(str == 'true' || str == 'false') { // Boolean - as = str == 'true'; - } else { // Float - as = parseFloat(str); - } - - return as; - } - - private function applyFilters() { - var $filters = this.textField.filters; - $filters = $filters.concat(sIFR.filters); - - var $ = _root.flashfilters.split(';'); // name,prop:value,...; - for(var i = 0; i < $.length; i++) { - var $1 = $[i].split(','); - - var newFilter = new sIFR.filterMap[$1[0]](); - for(var j = 1; j < $1.length; j++) { - var $2 = $1[j].split(':'); - newFilter[$2[0]] = sIFR.eval(unescape($2[1])); - } - - $filters.push(newFilter); - } - - this.textField.filters = $filters; - } - - private function sIFR(textField, content) { - this.textField = textField; - this.content = content; - - var offsetLeft = parseInt(_root.offsetleft); - textField._x = MARGIN_LEFT + (isNaN(offsetLeft) ? 0 : offsetLeft); - var offsetTop = parseInt(_root.offsettop); - if(!isNaN(offsetTop)) textField._y += offsetTop; - - tuneWidth = parseInt(_root.tunewidth); - if(isNaN(tuneWidth)) tuneWidth = 0; - tuneHeight = parseInt(_root.tuneheight); - if(isNaN(tuneHeight)) tuneHeight = 0; - - textField._width = tuneWidth + (isNaN(parseInt(_root.width)) ? DEFAULT_WIDTH : parseInt(_root.width)); - textField._height = tuneHeight + (isNaN(parseInt(_root.height)) ? DEFAULT_HEIGHT : parseInt(_root.height)); - textField.wordWrap = true; - textField.selectable = _root.selectable == 'true'; - textField.gridFitType = sIFR.enforcedGridFitType || _root.gridfittype; - this.applyFilters(); - - // Determine font-size and the number of lines - this.fontSize = parseInt(_root.size); - if(isNaN(this.fontSize)) this.fontSize = 26; - styles.fontSize = this.fontSize; - - if(!sIFR.preserveAntiAlias && (sIFR.conditionalAntiAlias && this.fontSize < ALIASING_MAX_FONT_SIZE - || !sIFR.conditionalAntiAlias)) { - textField.antiAliasType = sIFR.antiAliasType || DEFAULT_ANTI_ALIAS_TYPE; - } - - if(!sIFR.preserveAntiAlias || !isNaN(parseInt(_root.sharpness))) { - textField.sharpness = parseInt(_root.sharpness); - } - if(isNaN(textField.sharpness)) textField.sharpness = sIFR.defaultSharpness; - - if(!sIFR.preserveAntiAlias || !isNaN(parseInt(_root.thickness))) { - textField.thickness = parseInt(_root.thickness); - } - if(isNaN(textField.thickness)) textField.thickness = sIFR.defaultThickness; - - // Set font-size and other styles - sIFR.styles.parseCSS(unescape(_root.css)); - - var rootStyle = styles.getStyle('.sIFR-root') || {}; - rootStyle.fontSize = this.fontSize; // won't go higher than 126! - styles.setStyle('.sIFR-root', rootStyle); - textField.styleSheet = styles; - - this.write(content); - this.repaint(); - } - - private function repaint() { - var leadingFix = this.isSingleLine() ? sIFR.styles.latestLeading : 0; - if(leadingFix > 0) leadingFix -= LEADING_REMAINDER; - - // Flash wants to scroll the movie by one line, by adding the fontSize to the - // textField height this is no longer happens. We also add the absolute tuneHeight, - // to prevent a negative value from triggering the bug. We won't send the fake - // value to the JavaScript side, though. - textField._height = textField.textHeight + PADDING_BOTTOM + this.fontSize + Math.abs(tuneHeight) + tuneHeight - leadingFix; - this.realHeight = textField._height - this.fontSize - Math.abs(tuneHeight); - var arg = 'height:' + this.realHeight; - if(_root.fitexactly == 'true') arg += ',width:' + (textField.textWidth + tuneWidth); - fscommand('resize', arg); - - this.originalHeight = textField._height; - this.currentHeight = Stage.height; - - textField._xscale = textField._yscale = parseInt(_root.zoom); - } - - private function write(content) { - this.textField.htmlText = ['<p class="', CSS_ROOT_CLASS, '">', - content, '</p>' - ].join(''); - } - - private function isSingleLine() { - return Math.round((this.textField.textHeight - sIFR.styles.latestLeading) / this.fontSize) == 1; - } - - //= Scales the text field to the new scale of the Flash movie itself. - public function scale() { - this.currentHeight = Stage.height; - var scale = 100 * Math.round(this.currentHeight / this.originalHeight); - textField._xscale = textField._yscale = scale; - } - - private function calculateRatios() { - var strings = ['X', 'X<br>X', 'X<br>X<br>X', 'X<br>X<br>X<br>X']; - var results = {}; - - for(var i = 1; i <= strings.length; i++) { - var size = 6; - - this.write(strings[i - 1]); - while(size < MAX_FONT_SIZE) { - var rootStyle = sIFR.styles.getStyle('.sIFR-root') || {}; - rootStyle.fontSize = size; - sIFR.styles.setStyle('.sIFR-root', rootStyle); - this.textField.styleSheet = sIFR.styles; - this.repaint(); - var ratio = (this.realHeight - PADDING_BOTTOM) / i / size; - if(!results[size]) results[size] = ratio; - else results[size] = ((i - 1) * results[size] + ratio) / i; - size++; - } - } - - var sizes = [], ratios = []; - var ratiosToSizes = {}, sizesToRatios = {}; - - for(var size in results) { - if(results[size] == Object.prototype[size]) continue; - var ratio = results[size]; - ratiosToSizes[ratio] = Math.max(ratio, parseInt(size)); - } - - for(var ratio in ratiosToSizes) { - if(ratiosToSizes[ratio] == Object.prototype[ratio]) continue; - sizesToRatios[ratiosToSizes[ratio]] = roundDecimals(ratio, 2); - sizes.push(ratiosToSizes[ratio]); - } - - sizes.sort(function(a, b) { return a - b; }); - for(var j = 0; j < sizes.length - 1; j++) ratios.push(sizes[j], sizesToRatios[sizes[j]]); - ratios.push(sizesToRatios[sizes[sizes.length - 1]]); - - fscommand('debug:ratios', '[' + ratios.join(',') + ']'); - } - - private function roundDecimals(value, decimals) { - return Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals); - } - - public static function callback() { - switch(_root.callbackType) { - case 'replacetext': - sIFR.instance.content = _root.callbackValue; - sIFR.instance.write(_root.callbackValue); - sIFR.instance.repaint(); - break; - case 'resettext': - sIFR.instance.write(''); - sIFR.instance.write(sIFR.instance.content); - break; - case 'ratios': - sIFR.instance.calculateRatios(); - break; - } - } -} diff --git a/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/sifr.fla b/library/simplepie/demo/for_the_demo/source_files/sIFR-r245/sifr.fla deleted file mode 100644 index 2aa3f647f4902e189eba92767e211ed0ee15d8b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47104 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 INr#WW0e?>J6951J diff --git a/library/simplepie/demo/for_the_demo/top_gradient.gif b/library/simplepie/demo/for_the_demo/top_gradient.gif deleted file mode 100644 index f77bd38f97bb39339a19d79a31dbdb97c279b5b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1378 zcmd6lc~6rE0Eep}#c47U9WWK`hEW7OG8bTRe`s)sfNseg;G)pYMapGdJW#VuCek9{ zqE-vxtPb1Kavv>F4iP9vfudE~(w4rx-lOf?`!2N8Z?PxO2Y8a-@6_p(q;G!t3iA^N z#9;jXz`EbRw(j?>`#o#x-nDhNZ_VXhb9#LakI(M$+1y^M+iP)o%r1|~<uN+lCa1^X zaH9^l&hFCNU5L#ITb+>A0a_edi$iU;Ys_|)$)+^f0FzB&v;s!kDr%LZR;j@vGgwyi zW{J)$)|o_zX&EuShmG$(2%$m<eFLIzA#@2e2(*Sptzki<pV#R5YTcYlH>*NsmB@?| znFe4U08c5PNd+{afL^ac<E!8+IXLz~nRZmB9g%9dE9&7D&5%SrC{c67ssXWzEmHQ2 zl$ie?UwDEsSWEyD|1s_3CKzm>^z6N|`{hJZ#e>SK>W2@`JbL_u{PY>6hMZnk-_Y39 zOnu&%(ejc;XS6a|^t6u7uI`@RzW(mh1A{}{;gQiX?y1)klT*Cunc1n-`GrNn(wnzJ zLCUg7ELoAt<f{rmDgRER1tA#G>7kQGli6am*&UW-x5w-I@PvTxe(0{$F6ww$=(g~! zue+X4kiw$V8r)$OQ&o{i3uH6hmw0<{$(6fK_q5ECqyJ=FnC*GVuQ_swZ8*_OTc}UE zrjy+!lLXDj%RY%p>mw{hrIKSW&h@nl>FGC88`Juk?^)TsnG56nEKw(ZqB6?&8A;Mh z5Hc_F*~Ke6uLjg}jp>{YIrmyIEa!7N6=S7ATXts*Tm^PiY>Ug8ALv$3SLLTRWeoOc z`L##0n#KovApteHYWMniA|hn`$jVt5x}l5gy2Nif!)2q=-duQffy*%|UX{6sCx!<s z>KSre?q9NU8?^Lqdh^+lJN9sKZ=qswgzGX%&sXgEestJlgM~G@<D(-!ms!nk4&;=t z``k9YLNIpMza0}21C$1b6M(;iUQmI1TN7ZQEG$I~Y~G$pP?kqtW+;gfb%2tDBgUvI z_EHF{2QjqlFE_`tVO7-;UW~f>Fa@uEm;^G^|9tDLRfirAIiPuTGCW`NIJuIl2|US2 zKs4kZQY3pr&t>LopJiWe)l#yEfVKutJOI}IM)@4X5@@YpT@f1r>kIGTpoZ(q`B396 z@O&Qj4<`aOmxjc`)Vo*k@bij&O!!50LR}2@QA#Y*@-(vmc}XsyBD98^I)qLouH=Wb zPzrRd3>s6%r2h)&SY5nWeOur14*jM9kg4w&cIxz<+@EoVuF3F1L-))+mZ4`pp+0`o zQc4`!_b#&#?H6WJQMT-+9_0W;X?w7SQfM56X)NOqbQUmj&Ad3%uzh*w;b1q&GL5b| z^`<dj3eNm0IHJfr9=iX6IcRI5!8{RmD&8{r=`p-zY$`IZ&BEJN((ro_juda5*;`X& zosB+9wa&$J3|9V;X_<B7*P<fZLK4(wTTF}tY=Yz1gZ8D95mDDSrtWXIzs*QQ?ZWhE zoa5cOtYXLe?7WMP4HrsKhX_wP=oIJf#5*Mfdb@L_h=V$%SEuC(8?K9rUGiH{yKA*1 z0&poxv4`Bi{fOOeOvU~Vx2ihP=vG&4!Fe=Kv#xlwwRt(WF%2a~4@4y$^1{tQcrU`B PcX)MeoTfWC%(4FfURf4^ diff --git a/library/simplepie/demo/for_the_demo/verdana.swf b/library/simplepie/demo/for_the_demo/verdana.swf deleted file mode 100644 index baf03504737f3ff25b9a7279ef11b9a5ab06bb90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28575 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 diff --git a/library/simplepie/demo/for_the_demo/yanone-kaffeesatz-bold.swf b/library/simplepie/demo/for_the_demo/yanone-kaffeesatz-bold.swf deleted file mode 100644 index c812a79dcf951f14f4332e8b98ad9dd2b903a179..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76780 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�D@5klsu(RUKw!RLXp<@L$za8>kC3N>qct( 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aqNdr=v`Hb- z)Yw>z|CDoTvS<)ecd}KVH_w7Fxx7Z-Vqv6U8^1l=OU``ljjSTZl%t#`+O_`*)>v-S zTVJviN3(i}y;=Q74Nck#2L(;q(DihP%=+dPP<==cDORMU<3yV(C2kJb+rFA@f9ODf zaFX}@cR|%wTdUo{_I#`zF?|R1XyfXqW&5G&E%?Vz7ai-KcsJ4}9iV}AMEB$I%fhdW zhRf0=9G#b*%mO)_A$&QUnDD4QTGM^;DYvl7gmg<<#3zN0+gozMJnIp`AJb%Eujxi8 z6T`sUl6<d#S=rDBi1^e*MZVh-mzPa+L(FLySqD3vjo0Vqv{w}V0S%3DI`h&+@XT>! zv2LdCIXiIVTdQCM`C-aIF*4nkKa~6gZT6J~tG$Bjg-GGL8i1X%YJGw1z|BzDLeL8N zVb?*i7`ol#OTuN1(-+_g+@V&F&*X<*2NHRuiw#2z8I`%wDu|(69gR`RJg})UYr%Sk zazpt)iACiS=9MojmC^C=l}DTOR9eCMuz7l@V1p>0S>B0>GDX39nzDO&ZiotEFjr7x zRP5gnVKWSz)5(>&vQU*D$0i)6A(golNyw#rkEvl<*Ar7&p)FVznjdE+9UcrP{*!nR z?QtS`Sw4O+Z2osu<9CNx-x9AW5hp9(C^vhjg8eP`ng-|dyxS(De-!+uh%)=*Q?evh z+4`HNrrjR<9Pl?uQBfv*y5MuuL##xjqIuK9lY~`xZPM?*s1t;yc*~2qI~2<aJLvPF zWgzs$9Ef}w8y(q@r!L>4_N4=e#60Q2k~bpE!d@!Z;~>q{#T_p4g^jl!Z0HF90*!ra zd)cgr25GHYDNov@v**bc>L}fuKEi|7!XF=xTs~NV!!10_fs&gYn27$vY7x2N6E4^I zs>!9k0^2#GkTiq%t`3(tAY{Lq$GWUjz4_Drk79wyfXkGznQdZD`(=gc&CB{fX+Klw zZtDBVp_Yd{vSW(^>Q^mj&21C&+g(Z@LAy2L(V5GtZp@JV3Z8-SMFHJkz5b&I+7Gr* z2?@A!1%Em_NhO<ISH`bpJ(9X^iEk43eEQ}T%{Q+1m3PXelP&)p(uZo=Pp;P!@Ymv> zLvQlYe&c-)xnnPr4Ep~-`Wk*mAm;hjO*2|K{`XUNMr4v5uUFt_C;UY+%@nki0nEyc zZwyJfZ^s*DhK)tmaww{<4@qg~NCL)RqFNGT+Osz3at30DrQBN?`e&|JDTvM&O8}hY zplCmy_y#r#FrDu6+K=oyW`{oykR6y=`mug@G3+q0q$~jbEmEafZpBOfCQ=EX{yNG4 zq3!cFH#_vMn;8sCX(!+-Bdl-4*78LgJqj%gA$6BYFrpE@#Hr*$NJh-nofT7s4oNro zh;z@%iS^nm8pqoM9<F`{u^xQAzK1E^i9S7pu);ERO48%FviBuxO@E#>K%?yyq0ttK z7rA<sFrVR|TQ9XkdbkLLFEP3`Qcg9aR9ZW-hs!~kVR}S4$3hWJu+`v_>E&{$pt_=f zXpd_dA}!Z*l`3;QS)WahG(~^01oNm|V@knZ&#)-_5Kr8w`O8<!M3}#G>(cUn_|501 za+wbNp<If|KG)7d50oIGVZL5Mqc#OG$o3q3Hhrn=KGzoS;&PzYOUtR7;EJ`%c5)%{ zgNcSWrRzm~Zk-=yJ9Z_`ob$Bg2v6BwpE5zaj+f^3j#RLQz<g>CSDsB08KYOUsRjBB zN<>6St~lN(kXh*NBBg!0_vNzOVXIeveJZ;1vPDfOvHezW+Ta5#Z(`|?9CO{P>Z@FK zXsW)I?`3rmecCUwRrb%65jpc9y7jO6SKS}Z$LLJ`a>i4Ull|$Qk@rvcj8DJ0XJill za?e=(%RM9M$8EZt8}ChDdZFt}bm8xQ%vy5MaJ|ZYOET3cx8>J$lrnxZP))v^$MEgT z>zew(N|idnHWv4{8CtYoWulnVo+{42r+@24_cIvKz5Qok=T<NRjF5G{%H00O#E!ro zeX4kIUEzM2LTC)81GiRaj%=`hA21ymHa5EX6p+MDuJ&9~!bI;Dj5t`~ucg+cg#C6D zH6auC<0R_%;L@SCyx`w>Xdcog2lKw_6HFyHC3Erbb5BQD(>;vomRr&kaSQllaX*xq z9bt){$GpmHR+E50sjTM>mF%Sf!NPloF}1X|Nja`~$QaEOohO^&E@Dj%LNGEmg6)s4 zDHxCBWbZg|h5hDGvQ)iADvy{kb`1Ob^k?+<(;q@;-JNWSZT;Kxo%szR&m1ZA^ACt5 zBhkm~BV+-mqA+XbEs~6;3XbO3r)m8+%~KBA?nG^~#J_=UxBN^LVaOEQJ1yLlbRzhV z66Uxgv)=ejv)Vk(L+C{Q^*slt6s;F_OcV#L3WV<ri^HOnny(zVu0G!*=lV)$831a# zIRf+>!c*LLkX^hfiKIgJ{v$!7S=-A_L>4yq<X-~{Oy|t24<yH%e<nNwc-w37)~%mq zshw!{(MO>kf&4R?+T-8OzIM5)d0Am-{;f=j)uQH)8b((B6~tF9%#T%Qv@j4=Ulf1z z`d8h9D-sC=-5)mef#fB93diZIR&}_n(_q-_NOIF+#9P?qpe`fvvKp7=43D$!k*C&Q zj=!?P^wWXz#pM+8J^)5_8Kg>HuBBa0?5f%5^0>`d#gFeok}umJc=MrG^HaexB&NTr z$Qf$o^Vc<vUVR~XnN`84g;_UbTwbo-fRRB`<c}Ut_|b>=e_cHyH3c;~^JC`3@BZqA z^CP%59qpIAL~}Lh)r~^WN0LL@GUTteR}=iv<QDo?UtBN4hEMJ!{x|H*iAZ`1Ii+Sa zCGigqnK$`GZNH4|>Jl0pnjc6ofx)3N<@TN@zt?G+01QsuMW{;BXiNv<8%vivelEjb zFItl+KMWhw9ZWD;0g0SHob^+=WMRK{D^yUF8xHzwp^?i96^fphTXiwU;onE-XrG_U zF7V`vW@t3P4bKLLE^ESA{iMndqD3|jwD^JJG6NOEpsoK-{`>dgJ(ARU;J?9npgVuT zV9bWyoF|4ezK>YO2%EOdCtSVtznsi60!m55L}+D0Ujhl%YvcyJ!T%uD)_m*twqmgd zrNm**YOSrl<R<vvMdBJBYWfHmwi7-l;Q=tCm0)xXVm_~^sEI<U3~EVU1b-8dR9%9q zpCBDqub?|?3DQ@_o`PB&6`;>K=IwA-(DBqXNDSnSE!ucut<ah-Y5dxn)7K%Pjg;k` zZMN*AMkhU#x{n?)W>}!-c{GP(6bfGUDlKdb!iLpZZ_P{|Y0u?yYDF4(&{gm04nB2j zJ!-{{<vZQ5K2tU8tGJK=a-Yr%&5AM?&9O0b3oUt`A3W@qOh=t^`%@RWG-g&pPLZ+~ zjFHbSbaR@IFRWKkbt4JoE1bN&IvP8<=_twQ3w5j0i!i74Tu*nWg;dWXxI+%SO+fJc zgo%-gGjX41*I|x8H|wcxRgLuCnEdu+k&rul1E337nh!Z7>7JGpNDLdfl|I(v(w4oF zQPfnTxCic%JhA2|_H+-VT_M5Vzi3jfTW<2}EGjxj)^8T|p2K4+J@(PV(hz?Mn_}P4 z&2e6DQo!{*hv_y!;#{|y6c&gsBqmqtwD&nUVH+B#1BZ1I-7HewtimXzkJcu*Jg<6~ zJFDFqGiltkx0{PJZALv6dmVJER-Tzky3Jjjz%J%TJn0X*$0bmza~Fm47k-@=1Bw^p z!Ex0#Ezb%N^7}0gBvtT<#?e{_eHqADK^^xtJ%=MvfoPp=94|`_6KqRfJ?+BnFc35+ zNQ;<htI6xE=gTyc8k#WgIO)i|5ZkLhs&;}fo^-743!(oO?&#|+Ro$a*??%jl1UAPO z0(_LmU{t_9YYoi`lAd6?a?(h~&0{gb8nqbLyER`1<u7z{<O#R{MLSGqgwxCdazH}? z(XUwNQ3Z$AmX7CBqwBR}z)tt9Q+Fh7xRY#+{kpNDr_-y|@>p1$0;2*vro?X53jnV# z-R^-yVqHe(3u{-`iwaTgob%O(wBsn3LnKrx!PVZXdqy7=2d(VW<}PxXtJ%*Q@U%hc zLUnDmk80sMLNK3RYn$26?ybDsEIJd#Ht<wm0-+j%`e&&6p{99t#Cq0hYV$-yOv!Lr z6Ua;-dSG(m-EJHr$yZ$L{C#1P5!wg-ib5WRS+4|nY}Sk6VBKErb3XIx&DX6h>t87Z z7_xf0j-HyIFTl91kJo}jN{5A;)Ir~+j^LOqV9U)s0_`K;sf{uw-q-u{wD6|W4<Q5v zNhdu8aRiUtU3=OG8p{|!(?f&p`j+8|7FG<$`ZiO?aPM#&&o>eAI){6{&;d`&!GK9k zt1soth|FG9!Hd3#g=hG+v@A`{4pp|z8)-q-jn1;?0Z#oMtVIReC)bkHcQz+_!w1e! zGlp3uPRHsnF4XM4`}S_o=<YVBV>G070?RCnkyD-)4M*`!mH@yQK@nMvg~Kc_>xfS6 zUaR_rOLO|cHs);7tlMMM&Qg}=LBU15O@*!1mRCFU{P>&|b+)04vP5m;Ll!pQnxYsT zLRPonjk_uJhhCb30vB&i0M;u>LMIcxssg)xYa|qQ=Yai;G9kCKR1B5LQekeONAIjx zO2?q<v?koj^D*Mv5d7l+Bo^VDS>7kK&1X5M&VwY_XE|#RSQ0upiV<fOYTE3n+VB+M zyy!*7Q|OdS147VU%Jur*@YT_oJxnt1AeceTOcF(7!=-DW!H2%GsD+&cn-{MPG6Cm1 zNZf-2KjyeEysVrXc;>JKA+<AZVn^7jEcxU-Ew&KYvUX#VH9YaGxiR|iVrjuu0+*uR z6kgC25`=o=xr{fyIC##QrM(K#I#qH0F4VUe7T3tHX9>mMFGQW~?3srI>K^WMcNB4w z@qaKa3{9BJ86cMepY&qr0ynhgXXp9qEx^NG*5qd}`F#<^-QG#rCf8-B))i~u2-n3R z?!|*b)c(8&YUdTckz;3}$HKL;x9j{Jr%7adHuiJ)xNeTk`2<h_)a$7})iEtD24vR0 zZuR%6WdhB>99efZn2qX-H}D=yr*p)%KFmdyTG!mj?eLJGox+1nu(Qg#^}VHGk+)~@ ziC6Dt=ZW>ir03g8lhVC!k$HIfELvd)UhoW$4K3c`%y7kv`mN=(;f_YPRz~wezC`zZ z5^iRfnI_hwO?a5ch(mYiq_qb$^>7-OHGXevz_S6R+L&il?Xf>afSfyzPN%d!b4gw- z%?h4MnsPf*RgLGYJ@?_w8gJ_`)Gp6oTcvbT1}BNTuJ7K#?M!NT>U6-J24HtIaCd9h z)loydnX(q@I{tow0cr$IpI5n)7YOozrB4EM1suZlx3pZ_*EhxzPboG8q8skB3UaR_ zMLcc$6Ln`xEjMGiMcZ!|)g$~mJ->iG7U02J^ONOgiJ{yx+g8ZsS49FBL)klZy-6%g zBmmFB2+i<<!TGvQUAIHP^j>0(@kJ=XAqD9dPD6u*)E)Ha9K3{X(Af;db3=X4vdw%- z#DKQi1>j<Ei)1do(r%g`u5%G50VkmdJPpwUwI509knp0;<3a@XcIp$$y>mv1I&r1c zonkn_qe+L#TE~6;1<}r!jiHVZR8qlg70pFwO2RBXOjZcg58b`NSZ~hiPCtuWpmv}T zs$(rOKFHhMTntH?bM36mAR5k3vOL}W@}UH(g&Nw+jo8cynd4_e`X+Gq=aVYem|r;H zdREs!*JuemPdlNg#bdZ&%!yZ~uy<OKfc0ik;c+hol3a+7&Gq7dNZVLEc?we3*JRn> zyd2XQp-R#49R}r=+D^2&s11xBFRa-!yx6ZyYT|UAHt%=QPm<-+I_n0GMG13z?)R$> z>+lq>*1l@un>wHfaym1ZldMzWAnS@fX<LT}*4Z^bn5I50n&vp%JY3uemLZ#8*lJ>3 zL8M_Oot;;9lL&#&b2Fm_$-Y$YflOfz9cp#sZXB?k+aTBr=zhoY`<x<N*(6V$9UtqU zSP+85GL5E!#CpYwT#a)YY@*QHzhV49HYO_SAY8H(*zNV6z)c9AG=ILhI;LM$Z#O*D z)1%y!#JPHXS~|L^!L)BI0%LruhUzRK<3>%IYEv0&sl)7+wNIJP2N?4FXm9OdXuQ+h zyqrb8`bh{Yj(goHR~-_f?v#G9QnBml#&Op6B@Q%S-2ptLE&LV|1wCgu+C!MT3C&K~ zcgOa1ZgWIgGHY@eA$XaKijD`81f52&Q`Q|J!#fgz8Owu7S{#PQkaAkS#FMjjUR)u2 z(}V9(4Fi@gi=A244OC8>c}||zy_QK*>IxTzE2}H~*OO2we6Z+}UDZT~DEP(UpG*kV zIhzgSJPxq&DXgcK(s;H4mKwF(8iOG)-6^n}H)Cu>q3K~lwWSOn(RB&;IPbz2OKRde z&tMev+*ojecy6VX_)U|uYXD;Bj@ixkI-qm>T6{Tsvct6KSNk-MYgAUcX{`a%Kb-u0 z_3QF}2oq<c6xeMp&e$kuLu>GOgS)VPbyl?y{f+QYr@Ihg*09>jExWVt%pnd+o_rOD z@i9pWoHq85k0;Zf)h125ed=%)p`+pQBof6W^*2-@5Wce(jF3?++l5|JYcAb08{^{x zUjnz78ua(GeaEx(z9C%F;A1HD^jy#RcM{%`lDZ;#A@%qJ=ke#?&nOdPbh+l);h){d zB#H7m1Qm+EI>Ra`)3i?cXFCxGH{L7j;}kX_>c8US&azuBO!juNp3O!l^?5p2SnZw1 z2)Qq}^*p4tI#<uvU&U;9iZ^lD`#Pe#B?zxj_MB0%-QwJL(k_FSoISrN@b%;}a@`JR z)j@qXEL#<tJs-YBk)(FA#vJreC^yFie0EyKQhOdiGNl9L7IIZl*{e=U+&}UkEg5wf zG)Fb=Zq|u$TYD^ZjIwSWZ?p_bUX)r4PjJ$gB^ES1Q{gs-Fe5?&brko%3r}~*xR@og zD+w%hB-J^cv$;?AQjo>Ct+zDkx*b)8)Hm2p<rHa9Xd|+0L*hZx^}eO5z$my|Mxh!D zCJO`%i}YqBsl%l}t%eRAme5)ibpfLyRnER1^@Z?9dm<(~78wQUQLu9dxU_pR)P7*7 zCClnq=9qR`xce9w;E4Xuu8Y@)4n;+ToQMxcVnW%+hMUjjsK6G`!T~V~axGNzGUu@M z$>G<$dA7a|%`BdQ6NgCG=^Lh5c^!uWO&42?x_cD0;M4pLjse{;Hz5z+x-B2M0a?dw z!t<$CjF5VclOXNHF5!@*bGz^1^3A#%Z}86a$0RW{!^yYY-g+Vze0w0k-HF(UNnIsr zpv_6M5J!eq=_IRh>A7D39OlmC)ceu0H?ii>(R%3p^rDc~VO(^h;$GJR;#A+IX<)w* zRdyOI<JYvCV%zQ?5G8v)cBANFW7%^Cc=h^F0rISc-z;>J*QNP}D~rcD{&dAs;^7%g zRp>S5qqzAXiZ8p1qMBP<vST_nNBl{a+UZa=aBD7iaO2q`oaG31;+0o=hM~Zpq=pzQ z3lbMAMG4r<Rzq5KbsUbO!)Y5`x3qO3N9rAM0qpg<OsDOk14$EyuS;wVUha;ZK<7?d zJLwUge>$A>C5YiJ=S$aAnQY!58P_U52<>eo?sERdMl<JTJT@9KJK1RP)@XPQ+qJU^ zJP72(;5u_~?ssZXj0I#_(_L&`ccqI7h*}l5rkQu&YlwtbG`TG<`My*{7FiK{4o|5L zOw}R892Cd+$NE|SG>YF}>g9H8G_H4v+V_5tWx0vaZ3r9Nix#5MZF&p~+E%tEc-Vw$ z?*Srt3VKG9`Xr76TsHSOMK4S>Rrb~bf(mZsSs)WUA(mAfhqrXMWnA||P;Xn+Pmai> zeMQ4`xWVK|$jNBy^vr5o;y$hC**C1&4knQOMx0RfX`2$`7B@x$HLl?C()q2%b0PlI z(PuTh10r)1j!+~<Q`J^y)U(x+a&8qSPL-MulHuF@yFr-qGTQdj`1>D_85gTL)7(I( z+R@rgxR@C5{Hx0VR#P02$8qxR5iNl<3(&SZu=JG|ZQrf5BF{9wrsJWw4ppnUooQds zXHQU-NE6YeL1k47t(~;IFxG8~hSjC2=)z;8WmZVVywpb1!FY{O_tDmuyd>AHRC-wp zQvOv)9EDpR%KsINLaPB<09cT1Akf}@eUX2z*|??aw3ZzI+{9+3M3rFK!i5_iq=~9+ z((A{s+Ul1`WOMXHRzX8hy&Gb1fs?c_$`b3d)uk+qU<(!*Ay@k;5MUT(v%<(a-rty# zzXNjuun5}Bac9l&?OGqZW*V&y)O5b)vv~^J-Hl|;=p%MGc5PQ4ca)tt{F=?5cz)0o z*HmYA%r$Q8Y2itA)*Ofr7i2&2kKl~fI^5T6<VSk1>lVSBH|t9chpn5yiq+?8C+pT! zaCX2tPHad&g?sZP_4F9UM8#(2?m#|jd5@oB%0<z!-$9>6W3%CGwYbJeBF%R4>pURw zWF?9@dO`)>q<_X5T_we5C$nA2suJwEQG_g%X00~L+c*G$8&H#N*!nsRV}up^2Mb%= zYI%Xs@SwqH>8QG%8cZFJtwi(6dSn_S?fKzp)kQHv>2#MqPgae!FL0IvT5G>F>Jk#9 zy}(Jb`YKUqcX{lhc{a{h`276h!q}$->s8;M;`&TwZ(avbKkJA!AOgIm7fE>_G05j1 zreqQ!L3~PRU>dFZ<n9)cw}Qf5(-Z~ub*Dt=xyywhd;oc>SvcR+)ax)?2uk4H=Ly;8 zL6!mg`v$cl4w}OwCU%dq8J)%+Y7M%;h|s&Sd_Ca!j}%J^cuVq2={u8Z{2yy!&3MS> zIBhg*#=J$9c6)}i7V7Hmp25ivbkywU3Z9kj)p~iMlM95t21`$lmY1kv4yVdib7sme z#|mg`$q6l95Rw!u4mE`)35*-&RM2+BYPo8=Zj-UmlhK<_5;!>5=G$+*s&SW(5Mw=Q z4lCh4O_wkiRt<Jen#tJP4?oQm+M27J88YCc1eu~N{k0*6zHq%-tM=tBcG{;Wn8)6x zV%!<!yXM9u0@u!NVbTb5Iq}7ec{_23VCQXb8bAjW_ZXv|JpdPl5m)yvJA<lv#_nEI zpZC<-?_spcGc}qIm3hbuc)4qw7$s^ACoP4(!M=E%YyFjzeg^7zZKm)%!N%!9Kg6>t zMSEHW#9Aqi_w99Pp~a4Trn}WAm3Tqz`H?3Q40r1TWyc>qkkW2=$#G6KNNC8>K)ijs zZI3!UlZcy2^+af%cS6-csSu8d@LdjA7@oiFutG6%yFWOwyKhbK{+?7k$b=f3P4bp! zYb>MBG>&TV{3&(-jYLEFG?m|NB2^Wx1O%@()#IqXYa*kghPNPFMN-rlX<z(Ji@t~= z@%kvv7zC?Vy?7gA8%}bY&0xy+zne2He!8I}PnAwV61K<LTHuRwyIZNSTA!JeY6n@; z&6>DBu2X3+FxwSElktdkm-y?$sN<UuJB)`Du|SI#r*$Q@J8f>?61XJR1y3wXq-B7p zq2RC&%lBhk`S&m41(V~c#jDhw0qld?PHaj?ZE}x*OXFL|eLVta_X*-%GiaK#F~4nC z$7a+-2K(LtbB}Jk8QwYFYsu_twGdDRFlbpjS=PQXoI7K_-8<!EY^f@5(sL9mL)w3@ zqSlaenu;P3mbBpU!fi3NC1SY|^*#Dgz@BeeUqJ|t@tmgvgN(Err9(`}J9VXVr&-9j zX`X^0KXB#2TKW4pZl450C)_>DJ(<%!+E4omN{ZSNiV1YGR5r18y(RY&C3&atG!ABp zLW^i)2InqPhO#4;@_6hkHqQdC-?czl1PmI|`&YzWw@eo!C2_4dGJlJcM6~-IXuhq2 zztcY+RCM}je|+xd*zNlVIeVFS=EzR(PWOt2VFwMwoXvwuh&ZQ$9QaLDDxW8ziH{&K z+hzD=z}n*HXO;Q1MT<GM1;xvE^<(zP!8MZ&FGoL=tiFmX9Wo=)iN<pZ>ZCh1HZ;l& zlbG?J9dBsz5ev{(Z(8qe^-6AY{9-$^+JkXYryr+3>8(B|iHk*16+A6W1@@D<UA7wy zdpDPxqe8}|NPcl_B<o=Qefc1!VWp{u#;b5Me`XH2=?h~098+ko_I3u~Y3eSb=jxsw zE!F4h0}(c#nxV7WEDO4R_CMHr>)%XbW?e9x2~U`L!pVf0Ghya8%rIeQW@ct)W@hFI zGc&$nhRt`*+11r*wYq=6{iWS1ce~nUx9gGRdWwqppKG!b-Z#XWR${W`xXI^i;$f5W zA&Aqoi{0Id5gGYAJZu?(n}9rU^GOgF({iiCA<>w;h5Ekg(waB|qUp1W$A?_f+9DCm zU&c@XI2~C`|70dbIAld8V;>zL?ZvZDz5|=ipJVgVnzavZR(e7axUa)wzdDtm`ui`R z0Uv=Msk8SOQY9lXI>i$#>B!R^X7!Fm%N>B8JA#K1p7OByxz8wu?mk)f!cX9EA6`3x zS6J-Bf5dTru&s$_U3Bh$W8l;{G;1cRiT7UP?9w=dZAD%CM-vaU`)*q^lc;g1?f$!L z*Q{5-G=`bF<A~_2*D~6WwZ|l=-b56K)D(TE*HyL0NUe8+=6~Gk5UTA$?wmWH4=2#l zs~sE(aI+RX_Vy*XV4<}L;O20);nYrX^Z4l*70y+&CZ97#GkIGB$|S=hm`kxw2S_28 zJVBd2fqy|n!Jxa7M9|qMzcFLG@}J;Mh=NB5M9U7lJq34s$G>SKb-2oeuT*?cn{5bf zYYWhn*>E!{T+xzIldrAWQnB|`B@`q@IMzxnk^`o^t(Yq?6lp0eIs_u(d`UET+~>=x zJ)S#87U^~vdX{#v$hul9B<pK1(g~1LX4ajgwL~DzGxAxrQe2jb1U;WI?4Kxcd#XdO z12QOQfk3^}`5qPb2Ntwwz(nKvkM1#%-{P8r4I0(wzbj7x0LZ9TCmNyt%jBs{!6;`7 zR&!`ykI`=Oy|jfyEid&j?&E$3RZO!BCFj-;<B1<a2cJx)cjZ<w@3ke+IZF#yTcs=v zR+t`f>f2wV{fu#|Z2Naq0*S5L<z30mN^>*UEBNFuRd~huMxIAYU>~Y4Xklt1e=>9N zT=Vk|Cvj1499D5q-`3)Xd<N`$M2)SiRO7}<=2hNAF(b587kVmqbK3KH1`9otEH5L# z7jCB{|6B^#gUS|Rc3@E*gCRjPlWx(pc&uyLT0GDBNZz&_p}Ar+s4~xMW+o&6*mBZc zXZ;=Q3^N1bebpW}yVt1^bQk1CSgY)N@KM!<81lE$O&#Bu@|t)g4egT@7t-RSim`!G zh0Ir`vU3iX>=;*ct`D#A5?^gDCP~;7QaV(P3fIO#{#k~0R~b3D@Q%T%b%kEKWC~;E z%ZN_2G@9ewUBBE{p2lhT^vHGbbnSf02sU1`6=5Io4j6Avy_x|-)U<+cdb9SQvlJ~@ zT@J$pyBq9D(d<_gN9Ky<IL2`2I0?x>GzQ%<UF{{Nx2d<*+R<eDvHA+U?X6z0-PX#- zoX5#q2{*fXVa%HAqM^fe4_55C8BndBaU}eaJj7(1JWrfwzR3Mhy9p-S>UgT`&DEM_ zoR=+KaLDCVAbMi<k|jK!4R3r;glfH%k5wWuq|c<ZR{lMsPpf0b)nqsz9tfq%e2<6` zWELDGJ+}-O?sXU*ZfK{0YmL{;391*u{|sUKLVh~}>M&xc1u+T)AA~dAJDhHQ{&+`g zl760#xPMHzjIsvbF^1$p=ejEDrfh#jx)MxSp*z)Ppwj(LmKDX1267^WmebvfP}$yH z-db02yo<Zap^_|@)oB#uN6Sn@L$m&z+n``pQ#Sg@`R7P5vYWR8XtFbNeaJ4m@}usB zsvY!#gt3*Yw}d|UB7HN}o+Yj_?pF3j5j#cpqwOjE{&=L|0T6C#nnE@lzf|d*#d?** zh0}O1T$7b3B;T6R!Bbc847DrNxrcu0WV2fa4XY)=Q&L%JHzUipJF-|cgX8HS^MmW7 zkOIlH*0(1iUyi|gak;rDX8jf1C92W+E1kDxJ-4)`{1MX{2C_5r-V>(rqA-Y>RKI?A zT!MVUO2UjOD(g5a08HoUAXt(cDSd6D{k}u+ahPHqHHRZXhajQjLWfgw0>{J@A^Wm9 zKcFNbyDDLco%`(asupHEM07e{+!0K+A*rIqlhO&@N6cpnqwA$R%$#nIAOAxyS=?Fq zP?2!@@v_7&wse6%*l7}4QD4|-^?c2U)kL^h!f!;lXu7{(ec%@=slJMl`&$)cFf4?S zf2w#lMtt*Kp#5ps+0q`_I5bQ`B>Hj3;{HY#f35WKh4d<wN;UDg=oUd4x?H+@nPw7H zMPd&N&0C4t9x9P_B{{DKwhOUIZ)IEbGkZ6pmIlpO42N%r>b`gA3W+X(yh)0e8XNie zy2^C7pW8K#nMk^Dl|*NbYze*i86g_k0)-gc5Erzp1?E+kk_L9_TY-`%EH9+no9dUG za8d3pKy6ETrSx>}rdDQNn`AG{+<%-MK+df()hSaza?m(jDZ(_&3{S>xNT%@MR0KC} zNQOiMRIUvRfEKRkD58Fcv3LP6-tR4Xy;K0-B)3E8dA%q_ygtDzc-w5W#fF_mU=JQ> z!U0!Sujs=I@SFj1$*8C*$I987Az5J)tU3QQTud~r*oz55iKohlxNulYd8sPVD|bCV z#d?kwU>K*{3_!j%B)8?#@#s>Ox`_`XZJ0l$SBfN7^?IKb*X!yeo4XfqeSR^7Gcb>I zq~~3Ot=#gpU<>W<ITNK+@-dxDs-H6_RbXn$86nuPJM-@pbC(GDpS3}s+YK($mvWxM zotJ3^@}pJu#HZlVz4w$K3x&fi9lrMm#QEPfX*soO8m3V4SFz|`pH6Fx8NIGi2k*{n z;&nABO=nE_7(cnuK%2m51{PS>1^Nzbo>3Wmn7zK6t$a=Ap5pB<Q7=xcf0T*|7v`}H z0j>JBx>a7>xjAxB#qRLeFH14dC+nv_<e=@Y&|n*Obkswpqs~+&eN?SbA`zI3Bjw`J z_yB5?#pT^Sl(U9>EZnbZp8Ms=v|2*mezD5M7+ddMWLb&<=RRbTt+WnEImkNdO<uL0 z!1yd5>pU^!rzH@r8*E=6Cc4L-)z-S->^<qDU&prAOU5y80^F%5`vGuF{N~HHjvL%J z7B0O<&HlnFm#Bje<pSeJ#gM3CMx;ILHf6lmT%8A;KoVStwQmo8nGd|?b%PYIHo-~! z8x!a=+TpWws(UAD#DS}|jag*Z$#v~eHbjocAAZT7|7cVqzHn=D^zvww;RVr&?GS9Y zG8xrwAe8{oK<zR+<wFTGvxcuJWM^R^ckCO4&<@EdY;pE!Xq`X1YFkI)WfR2>w0jPg zmUUa6bV4%at*W;#tGACSU)E3|95ac=2mrQ>NJ?Ey!OJoYC#(DUT<$@4<6K3`WAwBk z)54yoTVbTLUZ39suJ5LHd;M6^{dMjb=4#^twkNN9O)`$jIdn7^OA6RhOp}yVL&{wg zj8CYZ4=fNZ<j@qaJP#&-F76DP?Y%GSL_66(pRe29R%r$?hEwpFD(vDsoNTzEx(74} z!AaOz-T~(Wd@1SuOE!70zvb%PXyet~!z~}icobkaFD*+1r}%F}RNKiceYBMhxV?^) zlWF8?@&p8~F?Lm1b~ug%SpV+kmT|ho;o52RdQA)gyid61+Mc3Ur>|$;^OS~Sz>Ykw zq>oCnqz;6`uoM?%e%p;Cn<jzeL*nKHU`8Y)Q>=JP4f-rO)uR+;Mf)L>q2moK2mJ-6 z@tX?YH*nuFIWy7ayo@#-x&OP8R;BW*O)cf9#v;D9a#u#jQN3SHNaQ-sm*;Hx8slL9 z=Z%kgBAcDbf2*sFHwmRu8kfPJqG8={YBSq$UUH1#ik)&eyvXzu!X!=+jcPn;HPM-> z^vt<IE!rV;XPS%3_GX_f#G|70y|*h%ahAhH^Hs0d;q#rZldSBa2#)>aGoc7YsHlHA zktwU%x50bTcsJw3D5LThVUVkx8K#+u2%}-^CdaZ+ylV!hV<A6%Ngkin<+??Lk828q zcydX*G^6_7M$HoK=xv1|BJh?C5s?Royi#GqegDiQc;2kDq^PrU6Ca$xw`g*#g1Xuo zEnJpoeHVj-*dG{)aE$RQdht>p-;OGkgtia*YSl)T<bzx7i64ApC!9s4#vGWf=<d5& zA(j^>lGJ?!)`r&^JMxIM^i;mIEi$+1rT;R0w=QJuS&+C@pGx&H{(SI~Z-wm8Lh3T! z<ar6TTd3&TGkaKsit7STcej@O5uU!kKEt8xMQ>vYSY7oezoE-C9T>aoJB8?<yXif7 zK6Lh#=Q#B;oB2GxS;7qnI}Bq^Un4^VIw&uYVHZdl4`5)naro&J)MLx7YFNy3$g!7E z${CIGC8_9YAJlU^8_T4H$CGRCb2R;Pg<bc^u|+6vFG35E8&CRq;Ec<D7*-J)cJ<ga z)SP?0nDDsYT`jq0F+K(aOgO&&v~jte<XU=CPRfjWOhnXGeWH^iW1#7dWKkeYp`qe@ z{d?J|p4(KB=30-8xt^(VP||i2zoF^#8JV1`9#ORMhuqM`wi8Z=l!l3$6KuJut?j+b zu5P_q_63Qof_(VkZvS<jji1qP!nzZgs74>vIlkr1hr3<d&s`Sv!=_pLOOA(i{M6?( zQZ_+XVE>t7I>D}p=v1J{SN&yLm8846Zd3;QnCmP1+WnO^OsBO$Zv68vv-kMQOq;n) zKR5ys-&H^4R6Y%F>n=UOTm4P+0(n|}HpyUZ)D20#zNFD8=D_LT-^*V=X$vs7B-54F zO_|fqCd^Hcr=(tw{*21p5!py)p%{9b<=&rcsAPI98DadR%sZW^gxn})S&2#d9`U^o zw6H9$9bA$6RL8MV4mqT~ytsK3v{a7|6T0(c4mASurCi!Wfw<f8BL+8d#sOQ9+^p1Z z*C8n}E{t=|FNg1Oh&hUtlNi~^0QK60p-i0BAI(D-L+Da3nWq7>@nNGhb8^{--6>YT zVO!&?`Jc*)2hYx=;6h}Rkj}HpO;fBCv#RDt9T7QQ1G?N4I-jIIQ6K)Ay3dm)=+%47 z<Q!eK@e~|AZ(GrQ68>$_zSMj?;uy^#iT_Z)k7sPcm=MQd#~L97`)vE%edCx3cuL}3 z{rc)6#n>{Wfm@LAJ9VxmL(azFIy|l!;D0!zB5Wq$O7CZ+I0Ns_r>26By}-3C#?Pi8 z9LJ1>srTy0s=4jq!SzDtm~gm$pWvjmcCkukAj?qB&%=70w0Z1GSx+u863fio$C3`+ z=dqob8c+3dehb3>RETKqSro5x40V1wYo6$H(|hCpoQg*5z!c!yr@51~ULVcoHR#Yh zXBN2OC;I9LEgMIU?7HiH6ajv~lIBmd{KVZdPux!gY|gp5>!=9#mY~y>OjK64+C4tG zpSm4OIW}RONooke4ouh_kIlAqYntfGjS`f5O12TywDURJ<T9K^N3d*`$O4VYU*&46 z0ACuK+H-bVx?FjwTeVBULPuMxn71>@7|CmL+9JF_LVd8$bkOoa_mSGX<t=~Tk7MVJ zZfV-{RIhw4<$5*2pQ(y$QXIk&SVvnD(N?_$KK2o}U;mOg<P!ei!qaS`Uy=O-CYD1e zNK6L)Q3d$_O4_@*t?^;khui)cAi5zi2<H@B$dwu7Fc{~TJZ8E;ahbgvxZnEp2Pu1- zyROhg<(tFlh4jn8P+kmPE!~|Km{aYL`VXk3rlPK4rI6&2ar4&gcGIcv(kVoeL305{ zeXIWB1+15z=uiylez{6%RCeyaTqx?AOBm_M27AePQq`jDShxJ6U|*A$ksf*-Z=Ym$ zYHBTE#z8Y0a^qK74(vPlrAt~y<0L{2=|}|5)VOo*QB*R?0N#>1(F~_-dS8O|9-hi# z;V6yXcAp^3^uqV>#x8TcdJhZAn*|R$`NV_0bLs?{nq`08-PQg||0TbKU;c|-cbX*8 zr0ua%&t$x4PGW=N#_EVd?64cd-~}}PrWmhD((XFEb7z<4MM5<kPa=odPFS>!W#wIg z)lbK*H?>;x-*4T~VZv_SEh@9^w$JhVvi9<$NBXJ7cujLU=YiFDDwiIrr_x&m(UDd& z?Zp8a-B<2kZ~a!Eo!)*{Ees{>(p-gR#mLk?7GSuy!>TX)Lp+&pho=ifUv^y`+U=gv zdws=)!c1`tEX*71v!!9%vhuGuOLENG8S4xIdR29;r?HcfF0d}ADDrsEBfcu1H2asO zu*|xT&Y>P&8nB;l#0Ki$akZ1U@~J+C$xcfP9<@_}mfJ=3L*f%Y8Arixj-O|t?<y%c z_3SP?>@K5sHm;P-tqC7vj$d@?yHcKrSI7+C41qr$pMZ(^eDzZB`eYdbYd2+{WI!>; zM@RI__x*9WqJ2TV(|o41Xb?IV7w8gZ`_AIdR4G-sr4$0Sd-=AcS(hH3oOWZ03aV9g zu^@U%asacfFB$_-vLwd_n2)gUbl`<U^B7ceQ}RAK-!J-Qr~4wYO=!{4rQ@?EwB^!9 zFq-r#D@C~@hsm~zBzE={HfbHZ3j)(M!wzV>&kJ{=v0%!QX-@ZX`FItdc{q1yGV&6e z`m0_N)Pr5p0Z;qHsLFb135H>5;D_7Su5alw3;^CU?pHatXPmceO~e#)f)wQrJgN4u zUbK4W8l2Yi_XjxLY(y3DrL((Ii4X*rz%0CAOa4xy2NX6T4mV4HULDw8ysaNMdv)|! zo#W8kqTos$Li=ru{m4Dw%I-0Vx7FvkQU_nPk#1ReP&p58;vyr~3r6(Qp={kT@!0Au zTD~4G5vSR;otQ(JvdxGk%}~P14(P727uI^Axz$GRJZ-!}#@VhmJDx60Uf%R<Lm`*% z4#v#scRNzce5zH+drv?ajsE(#qZKsOekGEGOYTDTlC*h8!|PaR*J41*-Loz9&)wd0 zX}9b!d!fW5+ZjvTn_wdHx$O#lAPFJdr6WPPGIZXlC;RI57Yb?6*&lT|0kd1&Z8O(& zl1Z;60gXkwQsrO?_jQ7Jbe1{HZd-?QJ3^XVv+jkLl<_SnH(*MNzu%d3fBx|$ZwF^T zkZ#g2s#Ay4MJlAd>=gHYF9%7CwRuU4^aJnYLx6Vabf&xL-wl^l^7vxnt6Q`L`mgkH zd+BG)WV2}I`{N*5D9o#+{^Cxl38WYvy5|}nwIlOH=f7iact~v(Z*l9ep3ZVNle*9? zPOiaDRwbvhA`aKrbTz}2;Udl6iSLqi!G&gm)s!FFQ0uMapH2I;$+lsF@#~*pA*dCd zbkAcA`&JkbJrv|v*FIakR_vWNn@c-WL1Yf)l#s^+s4~KOzc|5p={$et4`RyfAR#WB zL?%zL_<W_y$ZT_b{`PQf8Aw+sYKcO*=o{%(U%Lxccq?rz^Y_<;GGi-49g&PII2oX? zZ$J5U_wS=dIKSfb^kY4%8i<YQwl5bLM85nt*bKSpfW(d1;A`IXE=)_nyt$B7a}Lmm zQ2p29S|q{W%6Je8-TSPezM}*W$YMMNoV-KA_)^or(BF12*LzJ(kK#D0b!T3!J4@Wn zCld6L|5%*9H00&}^ZVI(vJP(qZrlEd3*S-a1mlQ^sHQbwM&azX?{cTf$m8ffGo!Vi zaPewx4(nlS#_%gjOSmLE7x3vUcW&`S?%D!&I1&~O=Y)`1{hIn?9#7NqRVT7&CQb@j z+@MPkAB)>VVyKe1(n#_uA%X7$T&lN=ghYkh?Q8<&stlT|qp-1R$|CI}j~p&GIj5(x zVPhnTWu0;8E=fgxj=!7NbP9Z`NRr@fimnrhs)QeS^aF&HGUx<|j5UR-%sNvRzC3|L zN{!f-HMwpR7uhVQ*&QT?)3j3>uCS4spkR5yA4Nge6hUeb5%Vf3ozEMUI)y&bbe^c> zo?8CLuxHLCWy_wG_#lb35>E#-G$VKBIjn(X{)NxZ&|$2uaC$!nHX&1jQL19BQwHK? zVZB3C!-KMcx%7*6A08qj>m%>pThf1jH4DjUZQLpDyx`5*U*RdRWaOo5+@YviBAYYJ zPu0ikPDR_^M9OV?2t0w0Pap!rpo5XGiB6$w3Hj~mEMzhq8X4j))ILD*QcdFNSAn~Z zT+p1Yvb050zDAKi&uv4*!P!F_+ooJ9DcO^q?3%$#<PYm|k7V1P_VU7&K($*vjZWZF zFr>oEjQb+HF<Flu)iUC~=xgYMK`)mMx0<}Z>S&#~SLScDUh*?OH%4dcPr42(ufqa3 zunkG`yUiGi&7O&S`1L9EA#@xa)tB-NAJAA^C#w6YrH}%&tFZp^QwQqa-x$p$FScs} zbV5N}x&*W+eGWDnOT8SYG&je0v2Q~WJ=qVl%-VSZ=_1hG)xJKa;?H}#rzi~=8KkY+ z1|nZuJE_t!uG7BKu+}mZVf6oQJqEq>&RS7pEpb{6q-@3_JMKug(~^CL$Rv{iZ_cW( z1k4L-b{tqE4qo`f6}K`{xVrcW_)kYyk3!s^5pNF=oZYy*8)fc2PZHyHFV@jm;{^$n zJ#@ThNz6BY!Ps`lBL_$)be@zcOGH>VDtR+isaSYkCwR}&^TT9DF^@1wWmcUoMP8dB zAKtKsU3@I45#D&Zu{mmv3zr&e9c}8~qFW-kPR{9B(L}>6rHrty{K2lix8hFN$uZM^ znTn<wALSbTY%w_Os-NCy!Kc}@vEHop+4D}z0>SKR+00=>={9-(^>tT<uON~UU>ipO zkFc9tRr8C}YLk`BP-6Q7PDQge#;#h4>o+<eiXjG*`}*QG9tjS0oy}F;vpuy<eR>)e z!!pEjamN-<=UGEwx~XR}?6IAfRL-?r2B$5RpH$}=E#}kdV<~pA_4p}w{(c{2`?ZKl zvJ;f-1#^?M@?wTrNhjM7Rfc_*bq7XULpa9d32O0l2AS}dvG7VaFU~vb&d9GSnz^b| zCPwIJ0bEI|8HKx2vWj%3wrKLFk8NJHu2}lmMT{qY3UP(i91=B6GFZR=tpZXPG{Muc z(elL+fZ&&-hcLg55`VnN4O1#ScYnk_-kS5Ai|~zk@{<;sjOBc5Wv6vOGR8D10@|4i zXE?uU4Fe{+`@boiYwn7hlD9kAr<}EE2I&`bvz;rf<NHItoBkarR2NMHgpUeA5I=7? zN*lZ}O_uoY7_`Os;(U(llm5%ryQyX)XMZ*S4n9nqIGMT8(Ns!R<->2f$dgUF9%JHF zvWA9F@vl`W_A<Vi@kK;+WsvH^*O>sBfypR<y{O6ZA?p>_&uyiHy?IOgAzPj_!ta#4 zFY?zFDv)BZP!cTp7sE?o_U+&eK@nb-<Zt^ct3_t);lWb$9!J19!)a?32c|HZ3{HNZ z8ecbGFgzp%l{aD3Lb5Ye$)^p#VdBEYy6b6RVN36+MXQw=euR}rsGppZ6u_{ZRgo7r zmEvW7B*JRY0|QYbr6SCFNgM0J(E979LSBznibIxu_tN&sn)kr!I~bl-yE$0c5^{s4 z#k~6|;iBVl=IL7YX^1}kJXr@>zB|h2cq2jOP6*?1%)9lM7uh9x{ifSC|0`|Nv=?{a zG@#;(RS)Ss2K24U=Ypk7Z{8~oA?=1_ITxvg$K+LSP{O*<fR&LP_ki6hB~0!eC8O4o ztLhf!SS>(GKmM&5%0M(g+M*mtpWVC-NW+xc6mM#?iFrK;Hg-?Q>IQci!C-|38(^?8 zUDzu1YC0s?3L<M5huCnCeU8^-kcD#+kaYT8EbPsfKL#+Q<_t308PdEQFsBJ;D!BiV zXH1R5<diW_n}W%3vxg?=mhW+jp_nAr&gl{5paI_ZKKcx$P-9vjZe8$NT?Z688{7N~ zL<_mhYR2j7nt#sheXLo@*7JZ2tyFvKN6#7ld2w4X-w~?Pu;-0P%N)@%ui4PWuq5)z zBtETf$bx!Bty^cO-VEO0T^$M=AVvoWkj`Nn=p+|e-dT4J!QhlVZ9Lq2c6XszWM^nS zHfLTXD@0Ww2o54F7kj3XfYJVAU8qZSIyqnjw$+I~(%`1uF&ER}8FzMsvz15Msv~vG zIV3mAk&U{ujQOS`b2sDkvV0d3`;{q1sJ|&>GGN_@2{$2QL{mM&3_Qe9vb{3nFpWLc zw(M2^If%i#%~xP-HW&Ax|9z~%!va?4HI{CSb?EBBv28M<6Y}@+NHVHchK-HKPyQzX z%dCemT_L(NlB~!AshfTz-%3)VlMcd!H*x26t$|^=n`?>z!s)T64fT>;t8-M@g<j;$ zpx+&<MIJM&GVc{n<qE*G{RrI8MGq4=>8C()I|H3%aaytZ_>!T1O=M8iB1!7Qs(7-V z2aE&+Eo=?@q77A($tg4zI*ESozwae<r4TAc6F=E=2eyStYWk5P?E%}CD0@rNk!m*m z&dhG)%cOvu)9L6K4fQiWJ~{{!`Cl)lC(Hxxfyd@SMSVllH~Iv=ZIhN-n3xLpD*Ml) zq%g5{Z*%e2mxo**(We(G&xvjR1$?f%vA&aFw`rAoK?WV+{Ya`9$AH4bv;<h3VlsCp zv&D=%CZ4Zf+?TZgy5<iuwC$G)j8?4{lfRg}DHG(XDRjf^|Jn~|R^x|AuFz_izI?H% z?QhV6N<zcjXW<&*KGGlj)66hO;FNwv7M5luOHowfC9k>MRh;tS!)BGk&){X&fUU6Z zJG#;=HO|+XB|D%W+oae6McvM`$H<Y432^8$Y_^j!6^U4WKx|(O3LlQWUbMvQT@gOv z?1~k~HSc4d{`7)_>?FxOSElf!esxzj2A(%+^Yi5xI?P8$e(QyjP#F87S7Fyi=uOgH zx*{!oj0laEh(Op#S>$Tt8ir|6H-CvqF^0h^kKfhEDi!oN)eAbk049)aPTt&3ZDvp2 z{<C=NflD<8d|D?y!+#yTc=lm$CzEZcqwMTwT~U?8N-=Jn7OS+)Da?$7Ygg`VH*Jfj z_aX1h7&S=X&MdavxNw*c&Y0h#ALCT9letmDFjIIb+kLJYXB%!>cx*iTv}5Af`doYg zT)l-~a|p&madoI>!ZVj0&Yl`=Sqf51g3fUhN=I2d-F<4$BxpXRXvM$)&)FXjWQvCS z5YWR-CJ%0J`D~ieE|X2-n#arfkE2($+1bbDDm^v5Awim6_4A9h(WQ@h!R4}Y;qK}Q zGoS7fDFU%R{d^q&u9zu0nw#IzJR}Pm7_+JM3H{7hC|J*YH(Pi_+dXlXHL49}#o4BR zwjJ1JDX{+f>X`aj?FcUOlsO~cyD=2xbCA>s?F!hI%{k|z>*?_Fc{8&pGZncGhO;;` z2j5@j3CX${Zvmthxn@0Nd)iDA<rJZwJgu79<#~&=E3rnfIdr}{h`auB<dBlDhn}W@ zu#17{c*jk1zOgEJq^vRB`L2@FvDc^kqOL|#acpniK72Q}XLXaJJiTM=%B!DlS}H=c z@f?MIBV<{8yv_33(?-7*+K18#?mkxXk~G<>p29V#-63GlBE()}^^w`A%^{;H1@%$H zpoH3<WM}M9;Bn@8K9pDOaJn0!9GTl_3aZ$o>s-(Qb>}21zoHi}7Ke}Kxs`9bVvRnv z)0`)9$hU#nVFle>-{_UM%t+GfBs=2?a!tXJ=aaMB98izHQyrtyDj@0Jy6iJ_O0jYA z*lvt9d!71pcL;ey;^x(%<Qq%qzeWJ*RuG8h9Cnzqd#zih004*78YgyEwQs2&Ra;J9 zkUUpzF@}ACb+<E{+d->$TZBrXE_;gOh;CZw-aRfXvx7c|X9dD0>m!w1uN~CmyqpSZ zk~%?;Vbdm2Be|)tM)voon&^=vtRg7{C`rSQ(9z!rwW$;#fy1qCs+jP7yoI(2t5Ix5 zWzcX4$Bi?9J;Pm6Oa^W+4*sZb5>p_btUn9;JLXHl(N2@E32@ZioZFS9cZN9bCB`+h z2>Zm^1y?LC+V|Y3_s?e6+A2OzQz!+qXSX%#bZFvX`fnq0EEspEOv{z7^3l=UrTlWO z*JZf{WKc1UkY6+U?8u)ZNGvBa+m#|)7a+x517wfX#FJM0zV)VSzPre(TnVO?D3a2n zk4M)Rf^(zWzpjzC1K2X!v}ul@luD>Udz#wTYqb6G@v0Ak$qYhuCK<ZsvOf{K-2^R+ z*_%riT=W~)TL&btR|7>4m}v#|i-Ta0E2N^u364I3VTKZ)X?aTI6z!^BcxU2_#FHn3 zglnozVy-B%#b7U*q9H>o%uJ98J-Ea8b8^gwJSoP~OteRfEPiQTzt5E@`Bnw0S1dgD zU()|>kF7Q$(`;>p^!ff2!Sp*_SSm@=7(psJL9===(-_mkvaQK_aDA3-dRFw&7>)Yr zY=db*ceD=1b~TaxFW~B^?~)UuT}?g?6c)<waKztm0g&*j`*_(dA4-L(tZW7HzV8rv zI}#j=x@r6mMp2jpH!&NC;&=s@w8|cF#9b!p7ZH?O9#O;J`$AE`=`CUNra(5dRAK{U zA=@m~AujFgXx;fMB|$9Ztx_Rv0UsmK8OYDF9EIl4JrP8sn`3vEe?KZ_Q(YRAkadBD z>t5{?Cv||oWkq_iSz*&m|HXg^D7YgH95T$AI8T(tn+fmGYq=oK&Ty8Q-8_SddRd}+ zLOs?tLpztk{PsMQHe^ZG177d2CsWQ(4LGpsN$l%<s!rGSmw%ovBO;x^OM0iHv|beT z>;zvoV>evWZ7P!RKltYFR2Han3h_T6w~9di*<QgxOadTYRRr69phbdKkTP%u^n}k@ zS81BtFcYP{rt&?+>F}8=a!jX}p0(c?7cyHBa)M0$`O3y$gM1R?yzt?DX4<L{vdDH4 z+1-5zALv(k;vTZ`%&!E)n6y>s#=l%Evv}^9ioiVfuyxOt+hoEoVV`+o+H~0Rg9I}W zg#QfwYdN!DbNV)dVZ>PzM)soEpbKU^aq}LKFbFqr;~kI^u)Y1WiBcz=(&P?n%baFx ziy})qvtTvlS3|URvNmAXik__O;7(_F2G^GA6U@lJcqs0XIGU_YtiprWO6=|rU#1%1 zH2R^rZY%?VbONn?-IzTmI>V@TX-Nx&I`Ko4dBc(Pc=6YZ?D6E4?)ds885o33Z42k* z0$taQz&u|yot0x9!_47i)Mu;F<KODPcO6PG<_%EfP2K)EP6d1ss$G-1Rw&}`sNQZh zLJ@~e(qD8r&+~89_9zI8Ic>8ag%C1*fAd=oaY`Oa@R_uvZ?Xn!QV4cjUqOIcds*0S zsc3DH?i6ioM$5ud;)KpWz12b4e)@V+D-}zWP1HHY+l!dFhDGv?^gX_ezU4%ZZ4f>- zW$zd6?e{%vro4umETgS2qdn0-edM0CTV(^!@4CTW5=T)J^r8(WHUcV~HocHkMzQX; zaE?t`5ose@TLvrV_Hm}aaAsXnc8)oSikNVYv=CFmki>hdi1TOKrxI7~$~#QGSY79O zW#GuYVl^KxbC=r>b(u;#!ezjGhi|N>ds}bYf`I%mt##W~jkvP~)E8eU(+lk9+OObZ zvNjjp!P`W!{Qr_mm?&bR<?JoBXkXj4yu7qwulygz8q;>4h96D~m<x88Y$Fwk`U!m) zw@kP!>pJDD<u|0iF8Ex%0~3wutTWn>TUv9yL=M-P#ZbB*fBXtY!*0{jI9rQQ&mO2^ zU1_LknUS<7{b*h8%1`x70i(DFMU6;{<IUdPp1$~c;QKYE4Jh(BGOQaAIs=VyD9$|W z`0MFE7vB6PhTEML<`fB9*l(^~OuQlV3K;tR8(V2xoPy!g&Fe<Fa<603p5XXzTlupp zT0(y&6AiCk9|seQ<H0__grurup1Nkf+{Mi-g(5ZN(~t4QJBUD^ZnTDmvOX$5(%upQ zfH^Ls%YKjzc&Kq1^oi<sf4U=A8^zEIqTL+}Y*>grb<hJ!$)*94?SR0-tO%T1rD4Z0 z@j-cO5B8>915TAep7rpaM`;arG=e(z#4@LM_p1Rd{Zc)2J-g7-&TEr(8Y0bvCVo9; zg+<=#ZHH&;o!OU};OXKic!XM~+=^1=sl7a;`D@sI5*FC3v^meMMXd$`eTLW^Il8rS z6BFC&i-ZuA2v)q5hnK6(lCqw!4JnN!q{M@bm}FC#^d=9-CG!LEM=462AuF2?6P&+< zfDA?bt&ug)QBJ<=uXl{_IoCS!-pvude7A!<g}2|>?_H!HZ6fUEqCJ?cmvwr~s1VW@ zr0GEGEnMz8O17dvlK4V=Em4^ho}XWY+d|lbxOq#<dR5D&1xtj9$;F?h=lfqB5A?V{ zS}Q;NrvksK9)2S_#*YUP>yZAQ<-3J4TOYWVN#pYlzLsy>!?%7IrrhQR2xrhsB8kab zp_$?ulbxsj<t!dGt3h}Jy9TvFBu|Eb{By3R@!l?(k{Pp3&^S87K3nwT5Q$eVDL2F) z=%$ROP|dRaan?r1D};nL3&F}<;b2TUE*be1`WM%FmAlVJjHM_U&sRiw;-{!%@clM& zmRF(&M}n7n#x6jbw%Vrwpz*`^LBHNx)+E1=qNa2wsgBz-sHAy)6-Jvds)pRX%pU|! z=l)D-)1&R2hWU5X^(fyC(}Yw9S2BqV$70G!$a^Kq!z-D{>W$3i*o0TMObREgfWJn^ zx#wBSK$9B+LX&!rBz9%orpL+1jEB+ByN%G4fkEvhlO`pRs)|3)WsW9--pQI<`8zt$ zH>K(WY9ge+4<vpP(r;?xQ5t2_N~awZgI{mSyV5Sa1h_xX6&7A6E67>PFW0~jf_KcL z)zf9*EgmW1x3IlG)iW=rb18SdOh-edH7i6pL3_R~lOt4aeCeW&UMgPg&T-9yWjz?X zyO2cq4$jyQsz$iyE2c^#(%D~Ed6Lk2G%;C!beD=dC`QmQrr;~(Bn6joyFdH9X(+Be zC%uP$9oLXm$?`t<+^rt0Lw()RtaSO(%Dz3BCa7~yWURS7o&OzsG{UIjP(TP@d<x<# zLxaW%5jI0phtU2}ZUWz_*F4Y=;7jRrR2)=^J^m->>ZQ_FoAd0hZ)mVTXywVgB@ei` zwTYSJg)sTN{AB*U0WsukT+xmJapd${kN3}Zg;z*QVp`NT=QZJWg-TELBv55bI6mSX z2wWt1n>WJ)|2%#&uUOXAp;jkU#_rnN{91f6*VMVa8$V~MNIOMM>l90qSlDYbXBm<x z)+I!KCGl2C*vYX;px68cqJj&*SKl5?-KTY`X^l|HiXQ15qgRy&)d^|DOXU|!VQn9> zz7NJEh|yr0Agr25gdJ+V;2P$YjGp0C9CMD`Iy<rDaNJ~^pDqGVv+5_)Nx$U&He1`J zuVoos!7ygrL%-#7sJw=)<&Eg(A2XmFafitvhupD8nS=xu-zd$E&OV{PG#v#4rWD_G zwqSpZp2NdzqGzQYbWoW-{@@ESXcnqN$Qc7DGI#<SDN2btqNb~yW`7;!r13RJr@L)S zGZZTo=8uf0#e14@zBF%lF8H=H`ow3$QR8ubC&)@${KZrf)2*bzHygUnYC9Q~NRDXB zz|125CY;_DnUH0?)eG?Et5=C|q%~^s90nm%VDujF^~oPo2~ek6az7JP*_8DPl((v{ zGW@)XvHB#{r1Z1Mp`vX1JlLk%b4W*PNl}nB4&B?3Ku?3d*?VYNI^3XLE2pjH9gH>8 z)-jmrWW@2#vTwbxwwT}1A(uI*cx}C~ik%B#y=qw^+spYB+@|`whVDy)uhM61#MFoe zB~pyy;)yiSMI=QTm$UuLy$w6Gm5PZJ@s6yV0GR@q=-%<eaUEX^=atLi&ZJU9n2q{j zR}|^O_<q`(yR9Wlo6F!Z$7%1rx)LL3?}hI*${RjoTJM#72x=30?877eO->Wto&e|j z1l5+v__DrrM3<_VB|lAhtKO{A?NVbtP;K>3Pz<3-eo)N^spCaJ`0a=E@SmVvKymyO z2JiA3+;p)N#u+R6#fBx}<R)gMu4;S>CA&-8vkqNHSH8H9lKwt*FkPETw_FGP>gcQT zS6E`#&awFE^A>auDt3+B=JlOyL{H_C9O9Jb0rW7g@NWP!pZYKQ-IYa=GWVlc5d0lH z!;EmYnN|8q$^ul)nS9?#Yy9a+4ri$cUN$QDY1%G)_1b3V0*$v<oP;~1bZT^3nmaZE zDO`FrBn+IrJNwp$%$b01@&_Yb7KfpnAe9CZohJ_)xV?&u{oB(*pv>w=gIV<0yWE7! z4A*n7)t3q9)bU1afn9b(;E+T*yS27SXX=7smh&>U<PGl*9n4T0L>;QvW9E3a7{N(Q z{4G-VyyFco5$-3}hzFhC!FsIK!d(ju*=vfJsTvf4edLX|ufr$urE?kFvXASV<Vc{u z@r5w|O9I4(Z?B^YaJTZEB)1VSr>*2xpmR-JA;M5TU-}qf>*X*%%gG46=b(fmn~btl z_lhnpNsISPPow$_Ks!&eaG1m4xLX*=BlbH8eG1o=W3aBr-;4Ik8_Dzsfv%lKk7NqP z4-+5nhBxvw#S>D)jdPP5xj%V+>|;%RK9Jm%YSSOkzbmo}DU!^ray17lu<5SW)hr_% zqVCrY8VWBWT_`^FMEp@Dj$y8#r%O`eJxG=ocZ@SlTch_+y$iO=EJo%US9j(dPuF;< z9FR(M`%)H5RM*sStocz5Em<+Vz6rwI6qg6gNIHd)2-bWO1KpA;HxmwMYCVR2oo|KV ztJ6q(e&!u6L^B?1FBJp5>!qr0F-KrJKklGd&wl{vF$wR8;?^`O+1ci8tg>z<TU09y zF|So_s1+ne)p(Q8xv<I%C~Oq>PoP+)bze_ZylPCkrNFnD45jP6V=>_=1(4+Z_!vNf zdw=}PjmGkYiS&bEfTY!fn(Xt`1I6$S(nD5*r}aaxg<{i#sfCpC!>@(5+yUX}MdkoC z-2v0`!&QUY=(FDeRRxjTf$a1{+5yw;1^3*6eisB$g8}QKuEl}yW7VVk0YyxNRtc_8 zgjWfTD*zD+p`b^)0+HWma>0lC6K=0pwH9G8;8G7-93)JSVg=GUfLD*cHNYnfP!da- zjz$;GCKAGwk}s7fe^03#j{ME~B~zEDEMMwguJh1vRUumzrpz+N=+5Ylt?J07Y4&ij z*bo%V=-$Sh#bR%|wLGlzqWfiUdNiG>7%`inIFVM>ZWJgvx0r^?C|Aguzgn&-7nW8h z!)&0FnhZ*(-6JShjw*ebu9-K_>Jpe<&|<cmo87mex~aPP<5G2#inYBuq&*{AJf-kK z(M6sj5@NfAo3+5*#J}1RYK%pj-Kko9`P&n#bBtIukMZ<n1`Wqhjd3kEmMrL)%Y0{R z)6T#&WGSVvU}ZHl7af9Ew^F}UP5g3Q@(Ru{ZE$aJkIbgi27Ukf0(N4c>-98>i?|dj z<f(3c%I7IM_{isJFc+7Cymg6CD_b#5>VlMiDdxHNd=-LS05Hqsdw8D9dEM!8r3JYW ziS#pJS!vS?g&=8qnXdG38-qX2NYI~&ZhgsVfzYnsnMP^if);l$vtd~+Sp|crG2Ziq z$SnHX`}@2`AN1HO;mUD}3uY@a1VIikhyo))VPorj5p;*Vo#J4fbdVgH{qWQbc^G`z zS^}(MxqqVe{xBlB_8t6BHLL*F{R>7b6dg;hDhml4@s_BisHI>hI8V1?pZ3C>b1WRG z0}@MbI{_FwHnnS@#yx~<?|Vt<B7UgipzNS&sCMW|&2L3Pq&+x2kgyMOeDr$~ZZ83N z(if<~Ugw7Z7lscE+CZRth`TAsL%#>Z58V%666PO;47`DRY>^tT$Ol5vAmHlM&yhUu zU_5&6Xp4Nodv^#Tx7@Wsbg!UBc~f?_FvUKgsDF>y$8!Y8C=}db?Qn*!=}8_dB9wO` z1}-4>YgGexYIS=5)0^6D%|d_JaJonWYm{>to?i%5vB7BKe_#s*A-@@%W5LYC{a6YG za1@FDp9*5gx&I^nf7zs<BklCW+JsB1j>_jmKEyKB1~#+!R6YuUVFZ-&510Q4!wCAT zv{-mPsmSau7tfD2*jMQg>Q}@|2v8bzuJ(}Z=qucUKXm%=Vv%4|;f|#wlWQh}bfwj5 zgB+z+T*`<h%mhU(f5_(Z>>0>=8HGZ|cl$J)N!}od^y3l7^X5)+QPlqm=&=4TvvE>? ztiv{OHqXR9uO&t=XFg8jM4ZmC5Q9q*4!<l2anavx9hzG26`{O~@P=;O4La#hh-5FV zj{qIMFc3%RRA2d)bngel$b28geKoxRMzb+mlM8&Q5Zrcb5U`Zqn+R^d^niW=v&Rq3 z9&9yDxJFhmYd#O9mf#)EG1vIFEVRb;kd^;N#6YKFiEwXnJmrc<U55C{NwP6lyKY+a z6`&a00v1D)LXwbHT@WTfflR56#TumoA8E|j9Q%FB*SxP(qOUZlG=Kv$9>%mG$S3tg z)rLH?hCq%v&s(}U)%JJ!XJ6p>>SGFwDRCrds9!BGRi1(|RzGw%6jyH}6{KT15XJx* zH#SRjkbH1GbSjiC)W!Egjy#fI6-a<*lILH49m9CPfe;@&3L>I3{LhEB>c6zjLAYll zK2@!3a+1qmDlv?}X)ybl+xVIhxIRrlTnV*&u!64Gw55aiDBF|e%B2f@uzxZ5S@r1* ztr_^Q8GzO5I<oqW*Tgakww$NsdOS$yPBX&Y)UwNfJa$x*%O8XO(;yk|iJ;{dX>uWw zMCL}$A$BHqF2M2lsY}!?d=YdW1QQ9BOd!mPNQ_A8EsXc)J@^^_26~TU&*oqqV(-@; z+8&E|7K5Mr!03=ZpI*<EIhZMKrRc@7_P2)f>SBg9e>0_0KM?T?aulUfh;=)q)(p%~ zrl6b2XERvRG7#N|5auc>Ws$o;sz_7JU(TpuRFP+A+r=j)E=pL8m6Oiba3~%6qiEAG z<+Vm6D-aIOU~`33t$vbhy|Y%Z-?54?H*5yX25=;7NU8p+2P*XCb~vjq5m=E0z3!;C zYJAQ;p;UM>sa_XQ<Vucj9B^epK@0st=K91<wXqs%;&ily7#NCh|DWPH7`W5GAjf_o zE`8#x+E`gNaSGZ(RE+;WG30vnd&d9vE8oFumZ!SH(*M<LB(*AO@ptQC*v^^^TkzS0 zL-QspS$zysAV4A+f#Fo_NJdfSpYPz@GUolacYUnlaD$Mw>{C0Ie_pJh<Wdv)O>AQd z!7qCgj&zw_*9Pq?K~*CVw7wr0V+OUS(#o3JL6iis&@TYs>-D7eCH(#EnL;{l7ev#I zt!xS2cmZjO#Jti_eyu{Fw?C<u(;dk7Qs4-3VfQUeXTT4YtoG+MhVsCkfbpkh4xm^J z2AbF!57O2Ia1%1G0Lx-`ZA0{+-tZf0L0>S!JVS*(#Ri%d;j5F>Dp~}BGWzhB$;3Hm z`dx<d-m6Zu*JFcRjeNMZs3!kmF$NeKu}}6{pMT#B!ZcG74wZn2RN}36hSP2c0K0-L z*?emlN|2`?O>7~;^j_J*<(MT~a_{kpo=u?8u|TSt2GIY(Uk&;b6M{ivEs6<1XnsIU zUz_Yu-O7Y_o(Xro!$)H>U*kQ9W7c1ztn}Y@Q5w|jb}z$MH>c*<?C|J80W@hpFw|W` zUX4MfaZPNeykO`&eLF~*OLZn&h!4IP#=k=6NQuOT;j7zLJopQA-oTy=O>7T`JaGeP z{IM6<R6n`Hdyx{EqxT+B-JaoAzoK3Zzz*d|WQieVNhj{?QuA9mCfRi}**yUah!%!F z5F^~cs*##4kj7Udq=FRIXaa_`viw&m6d-CN$sv`xF^q}tRWtHoSZf08k4Vn)@u(s1 z`hFBuQKi~jQl=76Q>K2ID_8!eqc*{24|5C3d1tjk903d_a^k6DZSF?uBeGg@n<27Z zJdWU*V;O>s>%x%gAv#rn&kN?d_v4*4V=E^3cLCrOpzVGic>aCata<szB-$D*Hytut zG;mWU(P!WI)9L52kVdX%Cfz@}X^a&<&}jXOEn_%GU_E5J-745-YbQuDnjF0^8KVKU z4hV1GTsm5$MkrUP12Ja)Eu6wj(7%L{*mO$WZzaeyZ9mqKW$B?-n0<#%+!hT|3&0&} zaSJfO>4foxFGQiFI568j>H_&(<uv*^19^>MOyT<)$ub$Cd^G!Ab>7Ti3QFK7Dj~N= zXm_yyzBR#pKQ#MirveVydv6S2M$lBdk^k=%z<_&?caY+LFb(}rU6RMV_AR1E(ryvY z5DwpG0=u7XOo1Gc&&W5-?hkN#lzT3NgrWPP386}%KF9%fYL~<uv|Z3eb#E<|dJvR8 zl?wEl1v<^aoIS-=|9vakWf|qZ3T4&{Rem#9h@fnEwM_<+0h8RD`KBbbgZEcTE!=&Z zK#x6d4IC#Q9Ixt;;kKvvQeU~wk<<Wn&uY+Q5M$7J&?8jtXHjTY=*)jDu<*nB8v&u4 z1m;Npv3lM5pVfak-BfJ%(4<GixK$jMCrRssZ1=i6Q^9oU{-+>`Jt)(DmMi&G*le`; z@D40gD;`b%HCkK(6CFO^7c6Xf0HXX1OakJymMZ@x$oX6!cW=9a9-s5MFL<xDo1-s{ zGGZZ8W(fYj6X)JN&Rr0(C$dx>7`b9ZTP^5;O!-*YiON6l0R&ti7ouv=-=H#Z!{6Q@ zZpx|_6YpQu45F<}T+k`Lt+k_o2NHCvv!0U*NzE0BTs8?I*Z*{@<EKX)1@Jow^G|4n z%Riw5p@hgIdcW{@CL?dS_mBqd2De9NU<M@ysRqps)^&w{BUa2}h9n{N={u3_)UGX3 zZSPk65|)9*F#Vn(gKeQpXq8UNytL%45|OUrh!^-&zYyX;U1acDrR)OCF!I48aiPtL zpyEU3ylZPX|2iKMA=vJOw(<`N%87F@l&ei6DwBgINeFWyghqwRgtj1OAZz{v_AZ8i zH$@cziTMN*Nt{Xe{&@O5qtsy{NAN(4VpTlh{?FW)FFnv5)WaKw7~~CIIgJM|2pJND zQ^@ca5r8f_{?ETjDlUfA;#dm6Awi`cLDoC|C02Ttx*hEsG-pBtcKqYX6<CbF>pC7} zjF<O_H+!{)eA)M3MK+P5zW0y%21G`9Mm*6)WIc9FE+H~0;yUd{(nz*p*VY=_Kt`kt z$3R9Om^2W%?P!K0ZPaL67i9#x!~yjQ<Z_(5v)|<(ROKz;3gIhZ&+v6{bx4b!P<7wf zX^(plbnq$k+x&4k|80K$Z{94TXp-nWRnI@~(QBXJc6c}J=Cl9fz0GI;GYEJ8{xb-} zd9Aw)6CQR1v1e$NMgJLwC2oz-6>YufTK2koEn)3M!@$2XeXl<{tY(q*TIjg?KAqB? z{XZh&KnpG*-T<J*>Y=ulg6#tzsi@9O{$>{@juaf+WPhpy9YRs@FriRoPpaK1R$R(r z>nO88#L`glkW+*H7eKVbbq*m0k=z~f4DsLnx`Eu28q`ttAS^#eFyP<Q9CQf<4+SEJ zAoG)Wi@y7FgmVP*e0?L5$6VA4`tK;>gwjd0JzZg4JXI#F5H}S6KbJ%LpBI9h9tzRq zkP+n=`D~C8C5VzUA`2p%qOsE;9XVUr|9;Bs{vO57Qq0;-9sf9w<lPU_3YlBc-kJV; z<RW@3V~TYNJIaA&Wy+z*aOGPtfIYO09gR#I6@_fd!NoA)HM)j7=snmH3j7KEbDPv# zfE6*nJ^1H;f1+{se+*CT(@pFx_U^|K{fzs<?8gBkXEe=_ja3pC;)7h5KyFEo*bscW zfqjN|mlhbh2?~V&@5NwSTmJqNX>E(|clGV`8-GYFgvEt3!Y0!y^Lz0U{&Ka-db?Fr zy7}VZ-*jPd)cAH6A_$a32(VuQ&Iuw>IGPq#7W;;pO-pN{8gG_;s;SUWChdWsCf=6% zIvK=BJgc?4W^U<|A0OS&Kf;<_fc#fi7W_JG(@x*CEHlRryqOl$PqiqIs^K1Ac9vv# z)o4f-8rf(i0N4blW7YAK`?hCEyN8SfnxVy|XUPvTd&i=<fmtnVR8`o4nu#{Iz3QZT zFi%zcaB(20RN!S2)9)k+-q&R>)!T(;lxYCYE1(4cdmQqsD<`v{u@I*+iP601{zsQP z?+MsuF7&-Wf@neGh8!0NRG%{gL~x&MEga9Zws4>IHH0e)4`eo!N9(-KRVmU1G;}X> zA~wW~|DOY-0MZ>w4yfpf(b~CG-zW)N*i0zHT4d<|tG%~?i>iAYMJ+^05fG445s~gL zk&-Uy7U>we83a@sq&uV$=^=(jN@5tgOJZmS7;>mHh`z7y|9;=O-}&9|p6}dq&+oV9 zSx>FC_u8}KS!+FOZ+1DxG;0gX{bJ_g=<|$Kza0B)tzxnD7_<8Oh3e9?O5}_KciRR# z?R>fU$`%UVZn&!`syi0E)uD^p6IulqXp|Sean_@z`UvZI)zOSm&HyAbEi^&%;NG8N zWYV(Vve{86PN%5X7FhVSH*G&ME#NX`y)sJ~=^dqyTl0c@<$8VkmgY30g~gwR>ER;8 zeb-Gk5$V`R>l*F~thWny)!SJZNcCt%(E9EQSj<pSo#`sWY|MWMer16hp4X$N%~A;g zh=w>91?L?Y6PTg9hXK%vMT)NIz_P=D7S4JFwQy8i@HAI>Yl*_nsd7jMLg+Qr*9EpV z;3vM(Vu0?NJdt6*?RrDtZt4vMKS5AQT;fR+(Ds^uXoAQ#1CQ8Q%iJ9vj4&Vk=!9Fj zP3A1^^{pbh4l`8euV0(a7?ItI<J+T9Y3A@Rv%bmLhqIQ5ph!@SX?fbrnb8}QX(>kD zR$$z!LG{pB28rLUa#8*<H<y#`$u#wGT3mul=*oWO936?<NPyl4-^0jWg4EIPJ^X^D z5+*Zx&2sHOIyvWU<(6NiX;E45-KvtI)S<GVgLOX#Neo#<>`~b5J|wAVr`DBlFSad{ z;22(Cv#<y?L&`K~W)+x!4nF`5L~@moWBC}C+zjc9TmU`AaQ35VikhR{<`@&T^e{K2 z9K*$VXZoD|PAZyzb+DoBEi$x@rn_6l_mgkrHWRxLptH+bo95&%?H8}#1>}tA7+Q4W zM(VAVw2uaK1)1ZhLT+oNlcT=*{H4S~p0-NLcU^1=+BO>46;$J_MMD=Sg?wmek`h7@ z#Aow0Dm8D_>7!(r&AsW-z(n_vXFJDyZe4wBNu9HFbYB-}0VyFY{!eqmGWXLl9sMZ! zFu81F(;Tm1`4;MwVq4MBpej4gF29tE58gyy%P_mF;Fru#pw^+&n^}Wo6ZWLlJ)aMa zBP#t?7kSV|F=-Sq^^C|KYBKjWI=R=|W~{EahOQekIVWo~pX%^}^^T;@)Aql$G)g5v z*q92rD+7c_F-eUt>Qk3L?DzDpw=KJ?QE1%SibyR>wFB0>R-@MGbk?~uzPlSLtQVu} zrjzcy(<^DNmeE?GA;0E#RgX|hh7xAWb`uZKYMb%rq_zoMA8vd;s|SJq&BoXQC7{l2 z=7~37>$g(T&Vyop3(4OUFt9lGTV!y0E}TEd=1&)i!`Dnot~KB+z618KJF4e7)k{r; z6LJ>3ISdL5s=B*;IKS!9HVl<M4qbdJrQe|L=&5B}5E1EEoR`V#L{|9gMio;$i+}v{ zKC5F7Y{0Skt2eWT3l8UpJ(lDvAP>y><K@FtSVT{Gnew28Bm)Zi1Vdi$u#n~-_f*;# zb}Q0M0&vTzQ)no@8^Edka#OK9;~!J4#rO^njT`VhV{?55`E;IVJkj*CW5Qv1c~(C( zj(*^GbM77wR@l4DIb!048atMjp$RrQ$mn^};~Gm3Wi)p2aczjKlA6-!-Gq$SJ78I0 zd3W^q52t4P9gUo+h}hz%(~kr>REjcIdE#Mx53SPCCPdEK7^C#3xV~2m^kCaFijzO) z&I@kmW+AmvWR^nmQ8`)o^#lUsZCGMS$9=g;n5B9P2jaK`2KAzoCymL=L`<Y)*6Or1 zrXNX9+uHc`#Nad+E-n&lDr*<*RL(gMxU~o9h|l_uKU#N#jMF0KZfCyOn>Luj-O4z% zG14h{m5Rwl+}1A7Yl*AzO*6ZF(AT(KMH#ARe_*Eu?th}utQpg8N86YdDG3g?*eZ?n z_`1NfCPK7wTQHr-ibaoX`jJgP|9%ucs@l*WPTsEcd(496{4OPW<3{*k@AEP2)N2a9 zxlMxxxQ7Fn>RtxIJl7iN538Dw@O4sHxn51)ZZ1#eZ>|5UPakU#F|lCey({lCXn@r| zKSu+>%V%#Hs{PKfKV_u?f70<<2=|lFE9Bgb{dQ&lvP6s=-XUEsTN2)@zZ<`<Ux`LY z6KBnXlhn1?B(vJl53Ho~#Wl3rCi940b6aa*v{UO%<`E)nOk!tw-M7kjttWI|wgVix zv0^iHwz(5J-ve#qeux}>%N@iHPaOLW2q0}s(3*6nl4A)+B51)aX3<E@w}K@~0&a10 zOe99!K{SNHt+=FZQCck&3ys-XuFAVI+00f13KrNNyfxu!F)DsxH7GGu+Ff{`Ec<L& zrz)1s3Dij8=ws7&(_O?%)F{(c8Q!lTf7v;Jz}QxC{g)h8Eay+NQk_iOJewiD({p#X zpSofQU>`K0J~Fm>^Q8IuwywoFzUytWTTuJ0dm-4r21U|~=VbDV_E#NVNL#Z)qL%4x zRI3E9NhuA!hM}IF8C{hO`NWskERfGkWj@_x?nHk*y3fl@F0wLbu;{h;r`3T~bzD2K zqtEOz2`5`mbdy>`6C32W-r##YM4<qguq`XA``qXD7E#r9fs4iSE7R?t1Lfx1ou#^E z>5VG`=($!KIu@S^fTaZToovt%-^ZIPoPBCLY3I#K1?a8sqd$|8zdOnpqiGc73U`u% zXJRiczW$^_n>zk<W)M}`Y&e}pl*__KM*Nuiu-#uUj~UbnHAvOvx-8PrpsWS8tpaQ- zbWsE1jcfe0<7gDidVh|2Qvl{wp%O~6YqOjJok^`ui4>QXLo(-8yXC<F;Ipt_Rk5P^ zbfk4um`^0ibm-ncT#=jSUfNF2_v~7siN{zYeUs!WY87=P2Q7Ow+3aYaJQrHVMg;wi zqzU#aXE=bA#mAMm8ZpZm2NuUBcfX+#4vt_9cDYkN)(O2&loDbb=rBQ@Lhto7*c8fO z*IS)lL|N9#=>N1a-r?}O#ZPVu_flR1ZWOf;ShZvz?-M-*VX;Xd_ud+IJ2t*%2$$x@ z57Scrv^EOh1COI$1a+c?%%6n?RmM3V_?*pMbN$HXH>YVEP)mGUvPsDnD;c%R+e~Zw zFvL#MPVsQNTuhQN&B!7Jy*V4Fe(<%TOHJ9>YS)`^`e4B#hMBGIH{>yGd=~6R-%Q%y zLu#PB6`FgyvNj`L@>2Sy(NaivL#OhPySkAM1ygM%B={3FT)|-<l>N3?tYTSz-0oE3 z<PJ_$jf|pQ1DMcwmp?D4SgmDBa;nml?xIs?(NC@B?*4A8v5Wb(0;~z6g^W|vS3%gM z9bW<QObJOXZygf1*{cQayeDIfahf*Sc@Y~uOW$Ml0QXd6BEM0_2BsSanSY<+dt&T7 z$p9Q<(tu14+k?Vt^L~v9eI0^GDfFgCV_hkgKAVg0n^?v~fHbS9#xok~J6&RTWehoh z{7*JNq+QjF581*#rq6}%q-KA7+)I92(f~+6c9Z7$udXK+)%C~~Y6?EXNYqlO%(sBW zcO6_;fiIz|29whYI6pm%Fo}eoYrXdiS;g<F!4?-7N9NAxAYCDUf9i>lELT+kE@3g3 zJJcGFQfooG)kD3#s-&Y@{M6x<qU1-$c}hWg%BjbxQ{R}0v|DKgeUT?^4|W167^&*x zKwI24cmT6bX%gY8lJ#nFozJ6uy{iBC*R$gI{xs`a3H_V#Y%hzzV|ms38AJS#^z9Fv z@v2IKt3RA&Gj1?j<DJKFlbZQBE4tuSH6b3jo|oBrm^H^`>r<v(qOQB$0}B{YTlnYv z5^(%;!s1xgO(7EOnb=cHk?HFV3hrlKm|IbLnHs+>UP`QYB3Bj}{>F`Rxwe}IiK3GR zH{*Es)_#q@+xo74E(M@Mmu9v0yux<u_LxlVQ194rQ+oKhHK!2zjG2y+P0Y?0I}3V5 zuNq1I-js)N^bDQCV|96(Vg1K6m0c>w5M_z>+fvI9Y3&_rOG;w|Hw)*hMyXBF_h$iF zj1zQ41(C^uI@z)kp9|}X8&umfT9i$}J-ym(=K1q;^dTqD?B5%Td&N;nT4>`#o6~p& z7M5R0wGk7e50YukTv~h_xqDMjo2``{rdzr4HFqrRdb#?Gj;6T!<#+friL9dzDAH!y z0kV@>suUt;XEsT$Qml~A%M(q;ID*|ztkF?(3JlopxdszFe5uSMivDa5sln7Zy1YyO zf~k=PhG7&W-tM2(LxK8sS4@HMW9qKZBsBWw%x|VedqRbu{T)1b3x3*Ek8O#`u1hwv zvPf9lDSA)$#>{+n%JD_EYcT=7F6~}L(s0}4$zA~Kq_K`1SCtG@b*O3wu_zBn?TX@8 z9ZDG!Sm1ktiFgkc0LO8o#dHb)2_aFjmcXBeu{lm*Gpe|{EVD;>cAiQN2emT0*tvFW zwE7-tS;bsAD!ay;do_zXMmp>%%vkh#m?(#=HO~b^G#E;(pA0w+-ub-f+0U&RW`low zSLw0-gnL|igUr{`PeJ^l`}U4;AYOe(H&Oc_<AWX|C#_riqcfhsO$ayeZ5_Q6MfOLF zq7!^7Cqto!O9&fVA^7d(UlDuP(x0JQTD6!><Sc5AfCqc+D3<xlYK5{03Hp0%(&`Yh zU=X=1O~+k5xT}$f5d86-G|IyJ@;&*&uYi$rIJ!UX=%K9@Kjn8Fb@$&!WEfC9H=3I# z{}u>?U#D-9w%fp2rCmq62u`LbQAfgX3}2L@MhO46u!+Z5&wm<pJWK)`_lhl2>{tee zPW^I5m>u#!eW)kalRC(seKv@X0zsplJe5&;bt-kEk2Pp1CTX4M+EH_*Sdh;HGa`mQ z*4a9JTd{56g6;0jRy%<gWF0s6meBXGlO1qbCWNoa;NB4q5;tJ2MrVk#6nvyUx;t!u zdt7hu^Y0OzW8t|G=4)gS@^IcDC!be_JW(EvvwiXU8(E4+KO^VW+7G_!K73o7k+B!e zQ`8jR><ySe=g$jF=F7pptSmS=yn76n&ATsoz#;0Ei_F==LTxrsywJO9NIwRoy*89- zjsvOsd8?7-K{P>&3~dEVouF69-Q{`lkj~41Jb$P8)azh2CwQ!Y_Q^<oK<@ODYR1R{ zhKOY8E_ht+^T_>9!{$E>JCsG2dL6yF{AyCEGRHx@5M-w~8%=-T8~6%kWVpP&=@j-U z*JZ$&C54P8&jUw%gO^~v_QgUQlIc@=nGzOhTgfE|X=-E3A&)&RY)9t5Nnq@Lnu0sS zyr9Z@qSUsnf+gHmSFGA&^P+S+lefdTi1q=H;O}Y4<>J8HBi9E%ThGts03B8wSbU;G z-%vp=?;eG9`mV}X0PNKSPEORqEeLIW@zZ8OzH^1G{s?_V-BQU8L8-|Xn9+EEviDW8 zGOpdA_xqxOwqyKCPrRH%$?9YGoXWJA@Ng8u;_k_)KFOTI_N{TJjbN214e;FTrmn+N ze?oPU6saa8^R}l^b*(t3mm=)IUhV2nB|{A@mxJ-pY@{^tuuK-SrFDLK!>ZP|q#|nM zZC}v%RR`jWOj639+LuwuwjT}RlZYuZRqcAECk<yJ#I2*%kOPJ|lIcie=cBq;%aWh} z--2!h+l(rG<~f_;{$0ByA+)%=1&Jd&B_W47T}Q@svKn5ZEa7`NJ9;Dta}ty}V|8l( zB_shPd(;z|Sdd@nzx%t^4YB;PibSk`wmFkT_t$A{U9J!47qCm&X717Bf2=tB%S2Ri zu_<QR-dfZ%e4&&HIw*^sq~A9S!v?*mofD#M^s-2cRv%KbOJg~hjXZzvYM;iEv%0<* zc5v5de8;QZ{)aWCN8m|oLOuS;(WX-xizbb6r3zcPgUdVI?_Qfxs?x=%3EW9*LE=nH zh4gpY>`FH2ttB(gWr|$bA2kkF9lrGS=O7MMG`S~$iDNhYd#XRd^3$wKcBmlQSilB( zoodx6><tuT`I_Sx58P&Qc<%A5B(LkcNLVk!2XA^crc=r^Cetk@qH|8K6kENbMz^~& z(psrTIbCk+#X9kgBGPLPa=blpEuLVOS^8?3ROv3m1~ORpA3(1$!!A4PFsdl0$FA$o z^(!+eW_9>admYOdHQAZFxBu5%2cgBQ6`d)51qw3lLC+<F?NJZhG+U`xIy1Sn3Keuf ziq9CYnvQGc!ngxrMjPiKVaA6H0Zo}+n${nLUkB-kS?T5)Z@}o^AFnzfL8W4f9g7$V z3q{eMno82h>$Y*W+K*2y6W;U=Oow31-2Zy-yh4_wIxcc4`s*WIDeDs{^})!6p?iN$ z^Q}?eF#dCzuj6txGTG`#%wf#T7R;U1WAwkA6vl2hGfh5~Rq0-icb7z$6Ra8f%&34i zNY+(|suBFInR$wj+4D!Do@&r)Wyz}sJqVg$rpwl^dur|lC|5>)$T2>9X1+-jGeGr_ zTGA<>K)0P*-$RT#)16u{<GwiEDqhT9fV8_9HQ<Hn0M*~5)@zhc4ZvKDjY$<1f-n-j z|1$4l_Xa-xT{F5hML~wY?tIEs%OWWq<!g!>R@(DLP?r(Rh>Ve?hRdq$_P)*DF7~_} z&zJgsHAikfcj)G*%e9=tb^dF<HPYiy{`V&z3<baN-#i&78H3h=)4p$=eU&&^K%B9F zDW4s#$31zfD^e5b$XP*~cJjkPc;OV-etZ^!I3Za+Y*_~WP=RhP1$m)9(7gATq0WWn zSA#PJ<+BxDZ#w&yTWRRaM{{McbI40M?E`0G$D#3!X;tS3h>=;KSBuC|DXQ(G0~|e> z&daJnEUU(;Qu_U-pT_8sSWoiG5%kEZ$7VMf6;t`&2!AS{fS$eDUfs!TzpV)%<K%is zV<Z_WH{X;ghEP=z<y0CQ7Bf`G|DY>J;Qa3p5N5VtIuK^Z|9frH!vBuWX{-_iKq4IB z(Fn+honFwg(~Z+Pu~{fw)f&r-Ir$PaeBVu%{&4t7wPyQ!EpYLMYvI&~3?cTB`6?|i zW_Z^A$^D9XzC!DKQC8J$a)~4tV(Xa*`l7hx$yQ;wnWJ|XKk{RI*7~n?i9NsTEO)L} zB7Akzfsh4qu#5O+47`ti_Ooz<sP|VYy{}=erF;ip*A~s!6Ib~Rjl>kwnAZY`3N9tH z4(qN-RhJ6$ZNGVc5estP_-6JCsVvej6=pQQEa=Yikq}mdNQ4gKajHoUX^=Pe4qag> zwguLR{!}%sf#2&QcoPnZNRKAHiN4E~r1ZbjC>c44{guH#2*CCTDz}buTk?Of{~|hO zJpPu1OYEDy{tfC_^?sv&Gyj*u7GF{kJp}iC{~*D{Vc#FFWA+cm=LpQu|0P&cW{VP3 z+z-M1-$NyA-WW~)Npjd{Mv#X7L*u`SC1AEFLWTR#t?#>^1VuaFZ;WF%Jpw)SAHx4q z7V|^5{_nvOdK|kM5pkuxzc8ZEI<rM_sy~JQrK~)ksxJ>+3%>X)kKaz!CPnQp%;kT% zu0}d0Wj3c-;j^578#rodP@L`)@a=Z00%^d!)tvg=?NnvbF?zF0cD?WI1B{1%6VA|w zb0ybtNPmLA*{B4r($;gd6+XE`KT}Uy834+CQVwoAPh4jDD5(6G<njtvYBMetb6&Z( ze?~Nv5ip@A5U<wwlK|F3Jfp1zh$ki9lF*FKE?z&3Dr2gHDLXvC{*v{i%S$Z!B;fgB z>Fhd6ASnjE6tF4^rBZpK1asq(4B+s?1Ft%f#Rcpql<?2TuDFm64>O^eN4q=8>kGW2 z9S%j|%Q}=$uu!Ulm%uVIfcg+mW{WCz>N2H<{l5wp4jm~Qb5D*0r+>*y7lqz*dbLKL zpc3wceG1o~yd+#F_CJSRCjV3Xk9?IDg)+XA?s!tA0$LHFt_>0KOA-RJ0n4Oz9#l4f z*3HoBdb}#``2&fmnE;GGiOIh-S{Z)H|7QJ>=-+30a$RNnmxdh6$StG)Iso%$6%IKz z>|sVsYv-gL_d-3FO&bvGv&RYW(;gQfj9(a$a>e#{5H3%pgq^X#{oO9H^RsQZzqhbM zS{fKyJS!ao>h%H=>|_Q|il088H>KFi3{G?Fg9@ymP<C8o0{U5x9}p%Yj7X;>KU-Om zloaMe_V<T}0Uh@;Gyl6RaY<{$)4ad5**%a-gHYFb(}TUtd`h`9vo%Q-;Y4KrkZ@*x z?`I%PQP?3bjhExy$aN4-y3-4gPy)e=^=n$_eSE)I*9vc%k^g^!weMX*HTo5AuoLo- zmC?202cE2h*~I0F%Qi7UgmQs##YnlVo2Q+$u){TTpBP?7v7kHhXchx=x3J{V`Q7=n ze-9)-vRt?fy?5t-qp5f26E0b)cgwhNP+>mm5zgKDPyY%E<{zQL8oH8j08(TK_x@wz zr!QGvy*K!Dr}+_{uSmbv<kdQOh+3P-FyE_1gHNh<_X_<^kq;I}s~+j=V(t!C{Eq0* zm3RZXL|)raM}lHr4XCB+Xaz>vP)pauJYQ9NBBlL{D29OUQ%_4rJJR>Z73TFxdpgqZ z^cS{DR^6wZ{)14R_=EU|<R3)RIL5_aiH(2elw5h=;b&sj+`?8A@l&5P8PqKGKNs*P zgIc71gY{7{>WlJ4dg0BzC1<2PzfJ`vYFafyF^yxi`E6`4vN)uoMT0-2Vz78}b#$|2 zRKC$BSHJ3t+FN%<I`N;?qyVR{*e#y?^|dK8(_t8!d42x?AvZV8`okWe0F&0r@MM#- z{Y%sq$fL$6M<U`M4f2D%6eV#UtC!PF+W?2(3&Pd4S3VL`s23KqRJ?tb7gWWr2Fmo{ zuc=~x144xbA7_ESF!YI?7yJ}v9XB`GDf(EmQhtS}@3~*PQvC*X3WVQCAJ^giZw1;( z4eRiJgPGF13!yACcm8(*M;FIQcgT6NZu&cJ$K>LW4K&wkkRN6F1x%oDA9}ucH~wu= zbantbaHR>C{OGf2Dg%K-^X5=2`qnj<F7}t`fG($dJ426)6d8V)(XO)DGHb{;)MWT^ z2k|d<zASzCb)n|Bo5L4wK3j40qLn5>@}7;$P@Qp8-El4m$K(J<ryq4KTXBDZnCB9I zxcH!^L*wXcRbG5Yl?wjn<k+2;em*~aeB_&&p7NmG95e`-m~XEh%Cro@NgiyD^W?LY zNAF$zw2a4?dA`~tOn#Jm^TKg14d-;w$D40k0sZ_~!K;?-JPqlbjao75|JenHM;}ue zpT9~FR)+mV`LcTqn+sk{XRf?mTz%Vf85Y5rQT;hl%VF}V{CsX(E0?rg&VMN2<wT)^ zUFJU%|CQ6*w=4gn;qu}UpQBhmUuHS9AqJ8Ep@Xo#^cUDXN!dG-M*?ZQ8f6xq-73DZ z&;6ANwDL0ee+e+4fh+QJi>c!G9o<pb!b_c^Vmk;sRd~-`g+iDT0#=eH2l0%++xU-7 zcwhF|zmH$K!`hAKd`{KMAQ_+;cCV1^u@>)3ko|kJr91sOco{KNV)T*$YX2hW`d5Lk z^WU89cGvfJmhRwn;XR3_5_>Kgpcr=VGuh*pyf5F|{}KT81GZ0I{;hS|aPJ@Te>dU( z1HtyX=-(H-x~*+B&-YdHR2$1;W6;3w0fj=PS9^--65SF3v1iOrZkz{e_$|zXHaZvP zImErncTmTiI|63~<WFIC^W5S1jc&<DkOg~PM!==dmc>tQrm}+!105&59^)CeG(^cF zOxYniF>{D48<scpb?IkKisGw)+2kMN4o)kVXH<fd5^MAS0NNHKyIg%YHq)h2v=Ddm z!Lr+Qij$tci3*C#ch>|&QfTrJ?}zfKuX36I^+qY1s%~Tr<>xu2X7xsa4xVzRWx=9` zJR=hoq#?g{+VWK_g|Zv%6w(G}tI;ZYRfgPx_!N|uY1ahAQ*!qXHEGQ+3y?UJ<COX= z^^jwyE;qYkv_oD6VQwKL2g%Jp=BTaC&A;H-PkXj@z)@PC8@MSbX9YKZ<DuXXGpLj8 z?>b2_Z|z}ggu7EI*y?&vGC9?90umIPIJjp%DVk-C8&Ua_Fq#xSHNge69NaUSY?&+( ztizrZ4Y9^$trR?cHr4|0y^MY{)<%C}tz(z!S42HBbT3fvOrj=TNOYgg=-|Hiq)d+$ zGi!N$YtO-b_Q`_T`*A;?p0C%+n17hp*Q%4e!te=XK<6gav#N02J)t#t=J^%a+Z3G@ zx&*+p?i;8Lp0y_68a(&>3hZr4&H{@DpsGl)w6OZB<PMMkKH5nB$Z6sQ>g{AWbH~>O zgx@vX)UkU<k7L_*y@;oY5WRZ-nmXBB&>zefjl}8tSbfa5SBT)d!$_B7i?do!$vxeM zsdFZM8}B$kWn(w@<V6;og62$WR`3DpI55}6Qz*H@_wT8&KC;UO-ON5g^&q*Q;&HV~ zSK4QYsTqP~!uexmiqf(S2}(YHiso0-B}Xx&T9mG5r?}=Ot+-B-l`67!33S0yQYf7= zdl{huyq)D<3$eQN23*hBhrP-znif1V*ruGjx?xu(!fWFdBUrxIK>p;7T|hwFW6j58 zVYql;Ua^p-dw3+Gtu#1zX*^=5uzL#HqGBCbc#XW5#7>fED*)#gL0|fKp4&fQmh1`8 zA>at|ZxQoglYp_H$7-6Qt>C}%2>hLg=ls2$--PVm9!vjV%Qw*zu%RpQNB{p!F#pv* zu$%&wXGWoaVC5z9>k9qwDB19pL#bPw1E#kqB<2kN6M<MG$&<r3H)H(q&g=uSHVycA z<@?@WUow2{`YJFK12<@*T;hiz0mXkN_<&wdM4BfMf`Oa()2C4c|8}%Lt1GdeKdVIl zRs56qe-jW<#3qvxQ4kd0-k<4Mnu&tgxv3?_CbJDJvAKlc^9%R75VnJ7?Y3N!Pso>n z9sd!up^Am+Fo`bjkiS=d`eEG3@W^Eq_Jc)Na!J|xj*Z=YV?-0+l=6tq**9<OZpIs$ zfS{DD#i!IX*>9aaBO{}tlP{@UVyPTdu<a#q82md3ItEejiO(iy0OVz`2~`G_{pN?u z){=5Qnp?#G1hEf6A(tC(1|cgk#QpTUo=ghWtTO=tfgZRD?Ewwpc=$wA&zK}+bwc8+ ziGg7#a$))-F{>TxoB1~zFWLhFgYh~EsUqpy-5D;GxJlmBO+HwduKcCa+S-Ov*|E{S zOn#N>Z<fpC8x-Nn{<uBqF6)eJz>dkw5N`ZSUciqaycR;LkMt5)>X#|uPg`A+o?l*| z3hzx_=sharGl@MpS@)na(_ikb3)^J=%RKt3K`qW?l?x^0{+m2j9ftfklXhca|0XM$ z8+YO8G0w6bPk4(PS?_`^g+GEPx#Nex{}`*IeIHJQNryVS72e`W23ugE^grh5=`a++ z`ORXN^_rw<slyl&JjPPtg|eVty;8eF0-Mfq;udNdnm)o(aamyRku!K;e8g6s4?lhz zkF2!*2qy2z?7`Vpjl)uz-R@-B$4<j?V9tXh-o_Iysz;8{G-H(~#ttcY|8{||Sx+^* za$b^7ntU-m-pz99)`ptXMe079_2vp6ocMF^^Kh(N@K0~AJGVdn^7}C`x<BON!`5TY za+}6Z(PfTZR#muV$jkM|)KKDa7s%}$nHTf(me;~><53&XMA3#5FQ)yWELWS0D2&5N zFwGq0KM|i`zUG-?u7|mn9P<-fE2c{@L0gp$TiZxBr^uBh*to6mj;2{<&ivBD4*KrW z=j^AW^-ou`HrEW*9M}p<b!|;>5ah$hZ<_dP&vj^=cM$cP94cNjPEz+l64{i?dDZlA zIW_W44xO!8sT>xR%bn@DROZ)?jOT1=nI_d(rdY2PIsIQ5379B0m*A7hq3?ZG<pIfp zLpstV(-pm3hxUYLskW8*`Zu6l`!aLmCe*8$I#r3zYwFGAa+Hi#*{Z+Y<cU99=YrL6 z$7DQ^m)`MY8P>o3;z725IZ)Mvqi#URnd@PvZoK<`HjcF$U7r48H`h;_XS(Bou8_N5 zazSaqcuso}EFPH>MTVeLY_yFu+7;(9Gzm4}<YgVOQ*5-3G(tJ@SVxf!qNXCi$*gZf z^*b0S&BJzj*=f>Z7AHX6PnH9CMb&U^#`QzK#{yx_R;YS>4Y%IE%?Nw1Eb*_&5kPy# zUpbWrl>bIm{#EVYWb{~UWrT5!{_?h5SqMALx@mCFYN-L`0R8tOgKIK9YUW1Fb3OHz zf=@2*M2q0TH>sbF4)AY1X{aL_l((SDE%%mj{4Q@C<;*g7i~4Fyvssqxve2k&i!-aN z_t)DgL0*6SUGB>}>UsV1aggR+7G3<xRpXcZ=9<RBJ3ckV#$mS?WKNO;*V|)nhA^TN zYvZtdAv1W*+mvVjPHE{{X4iFzn42MV=sjdz>tps!L&aVcELAGuz)i$83VwsNQ6XHG zWeS%4D)`NwI`p3VVX^i$B)lG-{j=Y3;*m?xn<i+ct8v08PaQIr0&{S90cfE2@AC!| z3n2DC&KtDo?hQo#z156~)Bo+a6GCs|)wv4tHWBg^9F{1Iw49Or%)#r3N9Jc1E7#cx zqwPdN<(E6TP9c0v#7@`RaMO=OR2nyJ`%rz&biZE?aPmibf~Z#Gw(Y0kei3%uArwqd z2&S$N&J<rHh*BHINvyFDG~1%iO?E`J%!Ewb#Ni?DWMt+a=RT4ryNCBJanT{bftzzA zU0A-R&5tKgz8<!1C~p+9TtWPyD_6%5HdF;Ycq9<7wJd@{Z2M%<E|bZj%r#_%zaOR} zlTh`Gi-yf+G~}Um9A6PvQG(uDHN;g~u@PD_^m*1)W33O)&fv5%8=-%NSw*@z<ct1` z^pGT$&+&oYmBvP9R>SqOKNDYyEoe162!HS44#C-1uyPA6Zk!E~{o_c2X-Gkb$k`V^ zP}h*>*_Re&f8elTzNl$?HckqO3VF?XJO)j^ZRGz%fK1&Z#W(-fqcUIU9XU=1u}iUy zgjMTl#lViT>~ywO*mkpcg%}mc%)?0E9w<0?O0+j|bcD9w=0&>o)~lLibNr}$*;}tW zLSY`J`t5BZiw;*JOoQe$i+2l+o7h|#m{}@fi(j4aaErY?YJhCllOqB4o%2ikAlYOn z2Usn22VLY0lh!<pyj@-jKEGl^B~$VW6h&AV@ua$=S~V>8yVeub<gbpA7kOwqNakTY zB7>jZb*sA>V?kF$6YuU;-0>60PS97%0Wvr-KnmMwO8oc0gx#KaA@6Wht<t!Y!IJ6C z$PMYYnyPpNu?ykH=t9y#`nz<id^Qd4VEK{Hc?Zp#hY9;4v%@(0_Z)oj)_9rZWgNte zQ=(uivB%U9yKmJZy?lXuxr#qP{q&L^1`;C$kz0OMz%9oYCY>A|we2yTsLyhMZ2L5* zR?Bs~?f^d;+KZVCb9ChOIGTsCCa4aQEwpngLvJ0UtfnV|*zI#HI41F0+By0AE8G%d zaw@8MET~uAC3Yc5ah&4LjiWgH&yDfDBrm$8NO3?vUi=^Mqse$a_RAQXzXY5KKMnLs zE7K*8m5RZ^Qwp|)l2~RfK9jbI<vd`s3pRC3po+^x>MgNJQp7VSGdN5E>MG0<GEoDq z0SV_uOhqf3|9NPj@%(#+{K802O3o_#{og(1sY20_WC_Y0m}-DIme>8&p?%xWZqmQ$ zcmFuS_0vg9HnlDGZc7kJPNm4``|kIu^%=a`he=P(Sd%pNjDs7j`r{apW#amkE8P#g z#xt0;|9{Wy@Np(H`#<3F8Laj^`;P{;q|?a^N0X@kihxfe%V#)nH%dO<@mm)DvlN!q z_fr4DvR!#EN<+r?qwlW2JdfkI&-geg|B0+(7oEq@tWo}=8-rv)muq>b!>@bHJhRVb zg*>(-er$)0+54Xtl}>&37I6ZHxE9MZ$Fb$Bbdw{=zb}0|h;-;)T16ceOzMQjEkl;R zE$c?=_i7TdX)(F1=yU0B_2~=7@~?gWeS)wL_crPT2F^#+;fuS@K1goWIR3rj{F%qr z^6ay=EATl0kI-YIUeYj&W_ls_l#n#ahL_=qD#*!F-fZvHrsN<WB;%}iW1YY_OJs<4 zRLJ-RBrCD8<dONPeT(a1x%y0l!87JV1ZHGqQM{mgEt#DD=X``|vcl%^hSl16l8Fj# zadpYm4?@gHHE-m%x(@2%GZlCt^nj%0`Ej{Af11{5!uU`({kbyuQ09zi)JtcJEk%v| z{M7ewyKK$3?km``ZvVsKNR`XhN-j)m7)hk<v}Afd(2vL`gsXHP@TTU%!hl+99vCB@ z4sMgwaMoR+1ENX$LH+UAh^31v0sS8Hky)s)JJnQDq`O_4)fX=&K&PpR%AkXrcVhR+ z<`l_nq71b~1B=u830Yxe-Ch#M{>D^4po>$e`lSjKvJ?tLG7o#6n3}3kp7!X|9gb#& zV7f1~xxrobE_Bx}nw>7rBISayjC^+XT@NZ|16-z#@iLJ{IT0}+%h95vo)72!EH+He ziv$lg#=N=-Ke!zmtADNLZ8UR9pc1CxhR4!#EpISQq%2p{&<G1N7jxuVT@Z#1_@3F% z4UX#n_|8bjKnGq53imRpJ)b2E`&L(0Ru|bbpFHI|&2IyqM_Lvyd#m$Me5W5z`5rk@ zRe1CbGHR`D=g=H9OzE^WuKn71u%qY{-L$u8J}H^EO+1_XY+;!4BG*#LZqsShdsii` z;YXS<VjMbMefrGQa9pm6$0d4t?^qjf@@>;)3T!{DTL5n`g%FJvX?p`CU>}*Sr=ly| zy5ry{aTgVw{HEGl2X&|9{Fqzb>9(U=8S{eF*~T7+G3u|kE=IliZTt2k*NsQ%i{Q0- zPG?Q~iR=DFqq@TOXJbx7`yUG?_VGs*bOAL$7!tfrRHR=6TtZf?a~D<VG&&7eTj&C6 zfy>DHb#j-O6OlvFE&pl%sqkt_Ct;Y-25{<(@KAkPp}G+U95{12RM`SlH?E$?o|qq2 zO-oKetJ9o>mw_{9z9-^`E>p8k-q5qi6XQerEqJZI(?!!+;)(y(=(Mo?#hBO7*~i+6 zGyFq^X+VuP>>PYTw54C;y>wo2!o5{B)#x-@Z8;67^<F-&KOt9$Sr&ncruqx|3xvB; z><hqnH{7NWgiv)s1=j|c+W^7|s*(zDZCE{yT{eeS2}%k;T~qf3mfdC$zRTiJ7lGM* zPbeaC*%(Tn3ir_4KW#!JF8imB3JUL@jyVh=KITp!@SzHV0Cynl7`#lBs_zb5I<8pe zPOTDX+#hna5CnJtmyhe0$pJBkB5=_e|9bzra97HGA(-HX*VG9iT)kewwGrkuaN-14 znE|*quAapnn!~H=CF`KBY5PLUUNa}YhvIOTy4igo^d$1o7*0O}_t4)zZ#qdl^q(26 z7v4P|^B6k$ST%8i4_BxMxO>A;UrHyM(RcS=I;%M3o~f#9+#hkZtOs~_FQ3&PrhEi& z)eUXiw<wU#><u4TcmYm;%O~~ll%`CdjvL|Vsn`NUu0q={=h~WF&K39l(J!{QDJd>m z4lfoCA^wS!=0MPKDXaw&&h4Fj90;RHYMeQJjo?mf96v3D<t8;wduAOcz)X|UEP)Nj zA7DgDjf1DtFgR!9(5XG*FiGF~Tm)eW5w-%>A4@`nZG<LHB^;a(3W?qmrzQw!qW8>c zFifB8;>+<Uq|qE$a9jo9gj7HP+!sa1hY&bf0pGz~sehQD=SE+tWSF4)MyZj1xFB$Y zzN5i?BhW~n|KM%6A0uvsz?6O7QbeqzHiaec)UIo{x>m!)u2^>zqdvgx%MvN$tktnv z_aNh}4e!9NY<D>$v_fEZ_hol5<E;6yP4}Ueo`oBDDJB+PRX4Hg7dxs29NTs84%Y%s z*@KosW7lHSK&PN3NNifgY26YLW24ovYIm_VaBx?x8=!5WWuZM|bFAA9*PgknhDM1c zMBvHe`uynCcK@7Fb4zoFpZ;AS=aJa9WiySRJ}!{z2r^gYtAO4}E^y!T*^$*Ycn;A_ z<jZ`!k@Qq=U8Qg~VC1#9B}K@H070P}kCwx8EyODjEZYpy-JhtQ!8sc~Quna%SOf&C zFoWP`au!Qk0tpI_?LaUy5G>S8&Itsg=}%0Pu$&1QIc3m7Aj}}l8JvwMf=&yuZUzBl za00|Ffux1`fg?jGt+0_(CN0E?8HB%o;)~g)U;l)EMg^{<C9b$7W#EX=!$L0xEe{^8 z6Iajyeg8zUgk@U52#`_h<Spo6y?-J%1J$pN0NJ%p3e7eL`zHXBmf>QSX{dyxg<ecr zC!U~#4`!Re{S$C8%SO_|Gd<8jw%KNP|Aaa!VfaYH!@@HW&_RXSCT?a0swCb7g=co4 z12fP;sF}4B=zykw0#%aPkdbo+trLXVCUZswswC%hS|{sfn}Cc8R7t!ib@vOmzU0;x zG)(L6%eoAgPpd+m)CFgC_g}gU2T!Zct`;F}T!v+)RheDX>j!lAwOodird5UEMM!a% zm}HGX-F;`5VR$X2z|h$o|1_>w<H=|BT1uqhp{QgnrF-T{AJw#f!?4%NtHaOgHMsR7 zUMn=y;dOmy--D;aJ#$YY;Leo5yp!$BY5&F<uOm`aH12gYIqjdeE_CX7XvpgbI@xKN z4sRSfJ1UqCzeqp%-aRb|obp;UJ7m{i1;W;+p><pu`*Q}vqJFCWw+kppzPdY>td%x@ zY4KAH$5rz(E?Fz^Q&qrH^RnI!MMs}?U4gA*s7-mM^kVO(%@els$1$@y+Jh}^27>f= z4}Cj7n%9-Duu1XC@t!uv0T>IOYnxBek-!<W=v)hFjYDoMjNp>A_hx+CbfdP<=}~Ea zX>ojtK8PlFJUUXi?qE;(eLO<l-5T|ZZz&T^V?#YyZ(SW5)p>JORK@9!53pHMZ5M(E z*4?xmOmyXs6_~U?saH?O$7$*)zgBy#rpXX)%}br_R%L4Pkbk%9c?p-Px?a9ge3ggf zcrREs(~&DeMMp;i{EXSOR`#ugp}eM^`S?_$$$2+nu(b11G>I`aZ><c1+FFN&_Mx<X zj%<3nFfm$f)w1f>Zf-{*IgrWz7MqdDmuQRw7|Aj3hwOp(!}RP}34`_r4`Qd;9gZq{ zdg{vdsm0sqF&&(vA4*1s5VR<BjOqc8T_Qu^4DoUMZ!u(ojN4})O2!fgn=DCl5xw<X zWF2G~g$V1i2JZP*ph-q%zn0$BqK(<ZJCjuI`bKl1HVodgShxw!&UWu^?|@Lnr4~CM zY{>G`E>YP(J-!(%SFa=8^$n}Dc9F`S=lFgi_?31;pN8FHnHxk}N;7NefnBpWZSW|C zrkP?(c4U%lXs1IqJR?;dtLxd|zy|_N9*_R$5V3XWazbR)$AZD=r@RgCf*D`QYsq<y zl9C-olCg$;A1|PWK7oGv+D6G6nel;$hK8D1uXYRkl!`2l|5NgZ;HdjV)ldDtM8UH= zmFJ79XY#>sbbZ@!1<VJ<XEF*xvrgX|J?O1j(Qc0r*J{h|#2oEk3EtWlB~y^r%;>0y zp%3%akfw&5%)Z(5qK%tQtS&vA>J)}dOR-g@B$qw4n~Wm~B$bIJm4UT;PHZfql|#<w zqP#QbnJ28C#tqP8;q%_F>)HvqbrC2%s+XE>4b#7iW^xDFbQ<YhesDTWlyi#ByMBM> zq;G=LedE4S&Pq??MX#cW$~l*vAw&(ToBFv!NJs^$ET1O0`F;L<pt3SRGvng~(`xq! zwKR~&O{B{|{|8*H*k}Gdxe1$a8FH5sqyUzcYMH65_|&Kv50-_W4)Df+<cp#mMbe~m z&@e%0-8RSHdjhJ=Oj>1(TeUg7TQf&Hgsc0CvT^k5k1D`+VN}Krnc78Iv~Fzn1WU`^ z2H7%4h|JCrjD_GT<F8uv&g+T%>NYGG(xb4iDYP1DudS>KIl2zEdM96(=zbKCi-Bfu zb<vNNmWM3%irHJv8{9Cy3!ID_u^Ou}-zO*CVyo;WNH~UWG}mtF)UOp`OSma@9>`Ji zwz0)NfGx!?S>eyJ)NMYe9cw5{xAMd3aI4LxT!dNcb22N0FB3(x>ad>{F+7hh1?`<a z?-~olmoYYj(nILS<6I`U%6um=Fzuyd5;oC{(Zp*VFrcP0l_vYv2?e7?6Ues7vdSWz zD(9Awo?-1R4to<!+of^-YU|C9!`i$?>mMQOa1Ln~STq5=m!2_&Je+-*t;_krq7chP z-$yVO2@Up-QVwp@^E$t8Y0qit=seQKbM{W7RrQn=V@;zM36}{rQiXk_#`#SiMm%cz zuGvs-rmq*cppUO$(oIe|*ijrq53tYE7Ah5FuF88Vd91P3ms^uWh8~jKn6s^ykS~pa zdEaz#Gg76JDEa{r>@|}M!G`=|!IQM~{z&y_;Zb0%O3>FVv%@f9?FO=11{bR+@pl9+ z#6+Ah<a)*Z9~PGFHBO|JGYv4VY)Exuo<UVaEj#m}4}XyJUD|f%b9Qc*V%jbUZC$<D zp$%&i$QxD?p|1=I7M1us-<zRq=m1!iCt=2LknUE4%6E8FNtK62*4LU6(UhBv*_w{r z76pHg)9zFKu%{v_#@KW6rvE^|$OX^b&(z$g<sqw#(lHUj8O|BxxXWC?Q3)e>0PjiI z@2E-8S;eWXQfcY&VeT=uHH*txJm|7_UY73YuZY&kwN1hza0tYvM&oyWrPA%tW$*IP z?PT$pLt8>s49NwbgD79Qj!^&xnsK}RLQq0Bnu(64##4mCx$JDDd=&jfUFUZqHRoQ~ zv-1=2)mF6Z1Z8!@nTL;mWH|LD@&jr`J=EG$^y{qmC-}EyLzLO6I+2rGb8|!^)?+1G z!R<s2V)@C|*>5(IztzHUDZ7{6*z3vLXc}hQVYfi+>7=n#=-C0x6BYEPhX+dovlS;q zo%KW;sRd7608)Wi{_GbMS`qh*Esf_JPlAJ-N%*^+3CnB^$vxUIrmuI#)H2pDVPs~# zB9PDfvH{x~*6!&GQ?z(HU03+FO@L)4iK{FGvm?f7a*K%cVXZygebw2BIPDUdYHsOZ z=`PrG&q1BFhYJI<l+H?bdn0k1_|o#y@HrFJ3qyIER5rsEMfKQM)V=YHVAfp4+an<Y zo271(<V$GA{N#MSJVIWfnNI=wBKeMYI&y7}Ocze)qRPF}_$*J_)|Z3Tl3n^fI3S$w zg6I8O6_Z&4?P{I1lt5YOZ=UJb%r<0vtDm?qu~&-;kXoE#Ufk@auX_$qHB>s59YOP7 zHYP&7$6adXae*F*a}|{QM7Z#SE@f)tgG$Hg7-a(n9$`PG)RdR{Qt}M-kSWwPEz zaF_2%2{rm^xXQe7t9cc7Cs<k5BG!0~y6l!*^eeD<(aH5#&EU6$6|J9)95COjvUBR$ zD}|O3*S>XTq~`29$POgb>_XqW7%j`vY0D5F^_Qy2<I`{J30fUMxG4t?JuW!!=CN(Q zFV4m^NH}>iWILW#@S_aVQsa>fXHmzdrx{QVLH7wU7;;D(IVaBP)7Yq6Ymqb1jW0JB zl6|o(9e4ggTBSnyEpq#%Lk_Yr%HiC`M3>uv#X1MoGbEx6^{i-P%Te8JgEu*+UhR_i zhcxZS&V`0FiBwz?q>IrXF#A=4bFzzLEM)}Ud+RnX0Gmq;w(3tGBn8TL9E-y@RAFIW zj+?p@E4X?Z6bK#jr#A4Qgbn(UUhVd0WkSw771{5M4$g~D*%wvZCPLVY>#FB!S_h81 z4q5RE!S*nVi|sh3%~MOJO_ZP~JH>lzpf{mFx57CG^L*hrQa!D)C;5dhCA3d3VOIez zXfM~!07oDXHUo1XrqY>I40llEKU$r~5Q<=Bf3L$IC-<YRTp@o{$_Mkz<!s|7KiARX z@v=Spk_vN)SJ?nh+0d(F#|$uc)~Q36sj^VgE;s>mDz7T3%yZ`r?-3fLnsxy?z1Kr+ zBfmk;fUMqu!`v!tJ`V*|AmE<+$IEUGZ4pBV^j-j#=ee5T=12lfBY47QVHJ?z939Qv z1CX6(t1{JT+O8`Mz$A5u8yPUs8R|kgFLWk9UJV;Y@Kq<&ZGV&KzPUtRJ~f*V;p(c# zaDy?d`@-3*e^cU#C}v@#8YEUPA)-1U&-B^ugJ%}6ay~AKVLQ&_lIEo{m=<c(N>7$5 z-Y4P`OR7&LjoI1Qb$t{5Rw6?6uK0r)z<Y>TudXG&TuLK)l))S<<o)sTbItk#UKl~I zHp}cO!mQq7A1!k(*L>26Xz1mt|Ham~HVwV;q6{xIw~x)76z6?)nQb+`=^s59@;Qy= zx`;+oE=DJ!oe<|0z|X7>1&8N^_=~#<MJcR<m9oyk(Obhr(;hi9Cof@(Z>J|-h4k1z z)4tA*IQy9JO&?i4>?u_smGIeiVTw5RO_r5(tdpqpgHU<L1MN|2zHH;VaGs>`Cj$`X zo$1(duLx$SQ~gP5U5K1d_`04<;7*pC*lT&mly0axSMBv(W)iLCi<bo#EHkFB-jKd+ zJFW*&QOZ8OPb4otp2%8T8yhL8ZQd(OQ(bbBKs*-hk+SNj>Fz;0dEXRePi?p?_E^<+ zels+*ADBAslDEGvKB~uYFcsGv8w5_ZyweSBMN4HnKi!1c*QT+Y*Cc7@In8jX98n?U zQj^P45}DHYd9+XMjZ&9(Z4$`AV~3wzPQ5btf$2?ST!Z9M)n}&_<=QFiE^&>{Hg(lc zjXQcI^7<+roZ(;R1gvR^R2qo8h-h+ql!0CGG$<2u9<j{i+2}KOUO}|=v<M>m)xh*f zI<GE(yl0$#v6slnaMu^1{z_s<9KmHc`s_9sQ``&so-Y&byRRev-RntFp%#Zs*JhyL zVB0#_koVl@h%A$hX^}?1%;w|Xx@mjUjRkQ;2Fb2Z!eao1_g#`GrsR2y<=c=&GzV&u z1(ncNx+hBRqZ2aAmHy^|3=>RgDsG2$A2gKSaE$VL4||OCw-Jm-vAVn^e67P*exEs^ zU}v|YJVpm;N)LXGUJ$J;2-y4r)}6|VE*qbg1_#q?P*LY@q!hT#PK+=6sTRO`rD+x9 zfqGRR%_Z9EOnR^aV)*D}+6Tt5$8VvXEGu8o5uH~B-?kV5cea1k8KnlYY1mdoaGYfA zL)&$%y)CoI;&n^Nq6aR3&2O?TO2g9GgJZ@qDC3XC7c3U!jexmH%pG5Q;(B-8#>EAM z;#n;(hRaiqKa155^jWui04^e1b4p>oikdq@F-!?n&uZTlysdU;=EtRBavvXVXUA;q zq4ig$kaFW#DsAg@pA9se&*&FeJ>%P=9<Xy^p`I~b`yn3KA`{QfoS<2*Q{z07As!bu z;7iT^o?L$KL~7x}xjrdbqwl-B%iZ<SMd7Nkl+lD`X1Ce$RyhI&a5_L&+8Zou*J*6u zFN2?|pcm;nbez;0{-jeTl6e?>M(MacpT>SDsO=i4W^0~-(EW%bGtv^7?07Ksp=Ho= z88*90biZX=oR`uSmo#`LDr#3qxOpS~<gQ8QROm{mQ12!%GG;xp^^5Ii9D|}GtNhk_ z_G|iyb#J%uq{JFHn%%zz=yxhoG;~3^gPE%L&K%<8ZJ+PgEf2)$xvq-ybtrC$va76R zm#@bZn|w_wcsrsM?DRa29k(OxG<~a*;ix(y+9-bVZH?U22e;KA)Ekvup*OrAhiN_U zfI(KP5m6Eutie%4McMckFV{18DdST_9~7mfG<g$V%sOca%80TOgepkCQ7U*85L^*d z&GR~$41+IF22DJuw(f^dHE&Jzo%yPYi3#``z!9%U5Nv+`q$WBwY7v%wSRaM3ksgq^ z>1$x@QemPfpU3Y6a2fjgfV4(xs0B!l%aA&x?O-O^4HiqWp6^jZ>{*VIsXZ(AwMM6z z-!XH4?P?-!0VT2ARnVNfzteK#@U;YvQ`NG@1&&+a%lg)5nb(SMHAJuj8Vg>$2QjLs z7W^2#7%A}Oe|O7ph(Esa30vX|vdTe!iN~D0&iBdlKEVRy{4E`c`F3yax6eAi-s~e# zfh|2NyHPTCa>%*g3yN->sAsQkV){0xBY`vZogN*d?9L~uk_{%fQCtgLiSTZANo3S@ zn*!X)k$N_g0POH4u@2V?1KV$pL=9-?ZI45A2Qknj7A#t?8+kihO$S=!DOhsso8Dxb z#HxN)t=3f0RB^Kiy9nQw+;;liy6-xg4z7;y9CGgVb?&Be9`XCUuqMZD@_1m(gPn^A KVan(Gss9a8mXT%v diff --git a/library/simplepie/demo/handler_image.php b/library/simplepie/demo/handler_image.php deleted file mode 100644 index 49c3ec89b2..0000000000 --- a/library/simplepie/demo/handler_image.php +++ /dev/null @@ -1,6 +0,0 @@ -<?php -// This should be modifed as your own use warrants. - -require_once('../simplepie.inc'); -SimplePie_Misc::display_cached_file($_GET['i'], './cache', 'spi'); -?> diff --git a/library/simplepie/demo/index.php b/library/simplepie/demo/index.php deleted file mode 100644 index 1481ba9172..0000000000 --- a/library/simplepie/demo/index.php +++ /dev/null @@ -1,295 +0,0 @@ -<?php -// Start counting time for the page load -$starttime = explode(' ', microtime()); -$starttime = $starttime[1] + $starttime[0]; - -// Include SimplePie -// Located in the parent directory -include_once('../simplepie.inc'); -include_once('../idn/idna_convert.class.php'); - -// Create a new instance of the SimplePie object -$feed = new SimplePie(); - -//$feed->force_fsockopen(true); - -// Make sure that page is getting passed a URL -if (isset($_GET['feed']) && $_GET['feed'] !== '') -{ - // Strip slashes if magic quotes is enabled (which automatically escapes certain characters) - if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) - { - $_GET['feed'] = stripslashes($_GET['feed']); - } - - // Use the URL that was passed to the page in SimplePie - $feed->set_feed_url($_GET['feed']); - - // XML dump - $feed->enable_xml_dump(isset($_GET['xmldump']) ? true : false); -} - -// Allow us to change the input encoding from the URL string if we want to. (optional) -if (!empty($_GET['input'])) -{ - $feed->set_input_encoding($_GET['input']); -} - -// Allow us to choose to not re-order the items by date. (optional) -if (!empty($_GET['orderbydate']) && $_GET['orderbydate'] == 'false') -{ - $feed->enable_order_by_date(false); -} - -// Allow us to cache images in feeds. This will also bypass any hotlink blocking put in place by the website. -if (!empty($_GET['image']) && $_GET['image'] == 'true') -{ - $feed->set_image_handler('./handler_image.php'); -} - -// We'll enable the discovering and caching of favicons. -$feed->set_favicon_handler('./handler_image.php'); - -// Initialize the whole SimplePie object. Read the feed, process it, parse it, cache it, and -// all that other good stuff. The feed's information will not be available to SimplePie before -// this is called. -$success = $feed->init(); - -// We'll make sure that the right content type and character encoding gets set automatically. -// This function will grab the proper character encoding, as well as set the content type to text/html. -$feed->handle_content_type(); - -// When we end our PHP block, we want to make sure our DOCTYPE is on the top line to make -// sure that the browser snaps into Standards Mode. -?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> -<head> -<title>SimplePie: Demo</title> - -<link rel="stylesheet" href="./for_the_demo/sIFR-screen.css" type="text/css" media="screen"> -<link rel="stylesheet" href="./for_the_demo/sIFR-print.css" type="text/css" media="print"> -<link rel="stylesheet" href="./for_the_demo/simplepie.css" type="text/css" media="screen, projector" /> - -<script type="text/javascript" src="./for_the_demo/sifr.js"></script> -<script type="text/javascript" src="./for_the_demo/sifr-config.js"></script> -<script type="text/javascript" src="./for_the_demo/sleight.js"></script> - -</head> - -<body id="bodydemo"> - -<div id="header"> - <div id="headerInner"> - <div id="logoContainer"> - <div id="logoContainerInner"> - <div align="center"><a href="http://simplepie.org"><img src="./for_the_demo/logo_simplepie_demo.png" alt="SimplePie Demo: PHP-based RSS and Atom feed handling" title="SimplePie Demo: PHP-based RSS and Atom feed handling" border="0" /></a></div> - <div class="clearLeft"></div> - </div> - - </div> - <div id="menu"> - <!-- I know, I know, I know... tables for layout, I know. If a web standards evangelist (like me) has to resort - to using tables for something, it's because no other possible solution could be found. This issue? No way to - do centered floats purely with CSS. The table box model allows for a dynamic width while centered, while the - CSS box model for DIVs doesn't allow for it. :( --> - <table cellpadding="0" cellspacing="0" border="0"><tbody><tr><td> -<ul><li id="demo"><a href="./">SimplePie Demo</a></li><li><a href="http://simplepie.org/wiki/faq/start">FAQ/Troubleshooting</a></li><li><a href="http://simplepie.org/support/">Support Forums</a></li><li><a href="http://simplepie.org/wiki/reference/start">API Reference</a></li><li><a href="http://simplepie.org/blog/">Weblog</a></li><li><a href="../test/test.php">Unit Tests</a></li></ul> - - <div class="clearLeft"></div> - </td></tr></tbody></table> - </div> - </div> -</div> - -<div id="site"> - - <div id="content"> - - <div class="chunk"> - <form action="" method="get" name="sp_form" id="sp_form"> - <div id="sp_input"> - - - <!-- If a feed has already been passed through the form, then make sure that the URL remains in the form field. --> - <p><input type="text" name="feed" value="<?php if ($feed->subscribe_url()) echo $feed->subscribe_url(); ?>" class="text" id="feed_input" /> <input type="submit" value="Read" class="button" /></p> - - - </div> - </form> - - - <?php - // Check to see if there are more than zero errors (i.e. if there are any errors at all) - if ($feed->error()) - { - // If so, start a <div> element with a classname so we can style it. - echo '<div class="sp_errors">' . "\r\n"; - - // ... and display it. - echo '<p>' . htmlspecialchars($feed->error()) . "</p>\r\n"; - - // Close the <div> element we opened. - echo '</div>' . "\r\n"; - } - ?> - - <!-- Here are some sample feeds. --> - <p class="sample_feeds"><strong>Or try one of the following:</strong> - <a href="?feed=http://www.詹姆斯.com/atomtests/iri/everything.atom" title="Test: International Domain Name support">詹姆斯.com</a>, - <a href="?feed=http://www.adultswim.com/williams/podcast/tools/xml/video_rss.xml" title="Humor from the people who make [adult swim] cartoons.">adult swim</a>, - <a href="?feed=http://afterdawn.com/news/afterdawn_rss.xml" title="Ripping, Burning, DRM, and the Dark Side of Consumer Electronics Media">Afterdawn</a>, - <a href="?feed=http://feeds.feedburner.com/ajaxian" title="AJAX and Scripting News">Ajaxian</a>, - <a href="?feed=http://www.andybudd.com/index.rdf&image=true" title="Test: Bypass Image Hotlink Blocking">Andy Budd</a>, - <a href="?feed=http://feeds.feedburner.com/AskANinja" title="Test: Embedded Enclosures">Ask a Ninja</a>, - <a href="?feed=http://www.atomenabled.org/atom.xml" title="Test: Atom 1.0 Support">AtomEnabled.org</a>, - <a href="?feed=http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml" title="World News">BBC News</a>, - <a href="?feed=http://newsrss.bbc.co.uk/rss/arabic/news/rss.xml" title="Test: Windows-1256 Encoding">BBC Arabic</a>, - <a href="?feed=http://newsrss.bbc.co.uk/rss/chinese/simp/news/rss.xml" title="Test: GB2312 Encoding">BBC China</a>, - <a href="?feed=http://newsrss.bbc.co.uk/rss/russian/news/rss.xml" title="Test: Windows-1251 Encoding">BBC Russia</a>, - <a href="?feed=http://inessential.com/xml/rss.xml" title="Developer of NetNewsWire">Brent Simmons</a>, - <a href="?feed=http://www.channelfrederator.com/rss" title="Test: Embedded Enclosures">Channel Frederator</a>, - <a href="?feed=http://rss.cnn.com/rss/cnn_topstories.rss" title="World News">CNN</a>, - <a href="?feed=http://digg.com/rss/index.xml" title="Tech news. Better than Slashdot.">Digg</a>, - <a href="?feed=http://revision3.com/diggnation/feed/quicktime-large" title="Tech and industry videocast.">Diggnation</a>, - <a href="?feed=http://www.flickr.com/services/feeds/photos_public.gne?format=rss2" title="Flickr Photos">Flickr</a>, - <a href="?feed=http://news.google.com/?output=rss" title="World News">Google News</a>, - <a href="?feed=http://video.google.com/videofeed?type=top100new&num=20&output=rss" title="Test: Media RSS Support">Google Video</a>, - <a href="?feed=http://blogs.law.harvard.edu/home/feed/rdf/" title="Test: Tag Stripping">Harvard Law</a>, - <a href="?feed=http://hagada.org.il/hagada/html/backend.php" title="Test: Window-1255 Encoding">Hebrew Language</a>, - <a href="?feed=http://www.infoworld.com/rss/news.xml" title="Test: Ad Stripping">InfoWorld</a>, - <a href="?feed=http://phobos.apple.com/WebObjects/MZStore.woa/wpa/MRSS/topsongs/limit=10/rss.xml&orderbydate=false" title="Test: Tag Stripping">iTunes</a>, - <a href="?feed=http://blog.japan.cnet.com/lessig/index.rdf" title="Test: EUC-JP Encoding">Japanese Language</a>, - <a href="?feed=http://nurapt.kaist.ac.kr/~jamaica/htmls/blog/rss.php&input=EUC-KR" title="Test: EUC-KR Encoding">Korean Language</a>, - <a href="?feed=http://mir.aculo.us/xml/rss/feed.xml" title="Weblog for the developer of Scriptaculous">mir.aculo.us</a>, - <a href="?feed=http://images.apple.com/trailers/rss/newtrailers.rss" title="Apple's QuickTime movie trailer site">Movie Trailers</a>, - <a href="?feed=http://www.newspond.com/rss/main.xml" title="Tech and Science News">Newspond</a>, - <a href="?feed=http://nick.typepad.com/blog/index.rss" title="Developer of TopStyle and FeedDemon">Nick Bradbury</a>, - <a href="?feed=http://feeds.feedburner.com/ok-cancel" title="Usability comics and commentary">OK/Cancel</a>, - <a href="?feed=http://osnews.com/files/recent.rdf" title="News about every OS ever">OS News</a>, - <a href="?feed=http://weblog.philringnalda.com/feed/" title="Test: Atom 1.0 Support">Phil Ringnalda</a>, - <a href="?feed=http://kabili.libsyn.com/rss" title="Test: Improved enclosure type sniffing">Photoshop Videocast</a>, - <a href="?feed=http://www.pariurisportive.com/blog/xmlsrv/rss2.php?blog=2" title="Test: ISO-8859-1 Encoding">Romanian Language</a>, - <a href="?feed=http://www.erased.info/rss2.php" title="Test: KOI8-R Encoding">Russian Language</a>, - <a href="?feed=http://www.upsaid.com/isis/index.rdf" title="Test: BIG5 Encoding">Traditional Chinese Language</a>, - <a href="?feed=http://technorati.com/watchlists/rss.html?wid=29290" title="Technorati watch for SimplePie">Technorati</a>, - <a href="?feed=http://www.tbray.org/ongoing/ongoing.atom" title="Test: Atom 1.0 Support">Tim Bray</a>, - <a href="?feed=http://tuaw.com/rss.xml" title="Apple News">TUAW</a>, - <a href="?feed=http://www.tvgasm.com/atom.xml&image=true" title="Test: Bypass Image Hotlink Blocking">TVgasm</a>, - <a href="?feed=http://uneasysilence.com/feed/" title="Interesting tech randomness">UNEASYsilence</a>, - <a href="?feed=http://feeds.feedburner.com/web20Show" title="Test: Embedded Enclosures">Web 2.0 Show</a>, - <a href="?feed=http://windowsvistablog.com/blogs/MainFeed.aspx" title="Test: Tag Stripping">Windows Vista Blog</a>, - <a href="?feed=http://xkcd.com/rss.xml" title="Test: LightHTTPd and GZipping">XKCD</a>, - <a href="?feed=http://rss.news.yahoo.com/rss/topstories" title="World News">Yahoo! News</a>, - <a href="?feed=http://youtube.com/rss/global/top_favorites.rss" title="Funny user-submitted videos">You Tube</a>, - <a href="?feed=http://zeldman.com/rss/" title="The father of the web standards movement">Zeldman</a></p> - - </div> - - <div id="sp_results"> - - <!-- As long as the feed has data to work with... --> - <?php if ($success): ?> - <div class="chunk focus" align="center"> - - <!-- If the feed has a link back to the site that publishes it (which 99% of them do), link the feed's title to it. --> - <h3 class="header"><?php if ($feed->get_link()) echo '<a href="' . $feed->get_link() . '">'; echo $feed->get_title(); if ($feed->get_link()) echo '</a>'; ?></h3> - - <!-- If the feed has a description, display it. --> - <?php echo $feed->get_description(); ?> - - </div> - - <!-- Add subscribe links for several different aggregation services --> - <p class="subscribe"><strong>Subscribe:</strong> <a href="<?php echo $feed->subscribe_bloglines(); ?>">Bloglines</a>, <a href="<?php echo $feed->subscribe_google(); ?>">Google Reader</a>, <a href="<?php echo $feed->subscribe_msn(); ?>">My MSN</a>, <a href="<?php echo $feed->subscribe_netvibes(); ?>">Netvibes</a>, <a href="<?php echo $feed->subscribe_newsburst(); ?>">Newsburst</a><br /><a href="<?php echo $feed->subscribe_newsgator(); ?>">Newsgator</a>, <a href="<?php echo $feed->subscribe_odeo(); ?>">Odeo</a>, <a href="<?php echo $feed->subscribe_podnova(); ?>">Podnova</a>, <a href="<?php echo $feed->subscribe_rojo(); ?>">Rojo</a>, <a href="<?php echo $feed->subscribe_yahoo(); ?>">My Yahoo!</a>, <a href="<?php echo $feed->subscribe_feed(); ?>">Desktop Reader</a></p> - - - <!-- Let's begin looping through each individual news item in the feed. --> - <?php foreach($feed->get_items() as $item): ?> - <div class="chunk"> - - <?php - // Let's add a favicon for each item. If one doesn't exist, we'll use an alternate one. - if (!$favicon = $feed->get_favicon()) - { - $favicon = './for_the_demo/favicons/alternate.png'; - } - ?> - - <!-- If the item has a permalink back to the original post (which 99% of them do), link the item's title to it. --> - <h4><img src="<?php echo $favicon; ?>" alt="Favicon" class="favicon" /><?php if ($item->get_permalink()) echo '<a href="' . $item->get_permalink() . '">'; echo $item->get_title(); if ($item->get_permalink()) echo '</a>'; ?> <span class="footnote"><?php echo $item->get_date('j M Y, g:i a'); ?></span></h4> - - <!-- Display the item's primary content. --> - <?php echo $item->get_content(); ?> - - <?php - // Check for enclosures. If an item has any, set the first one to the $enclosure variable. - if ($enclosure = $item->get_enclosure(0)) - { - // Use the embed() method to embed the enclosure into the page inline. - echo '<div align="center">'; - echo '<p>' . $enclosure->embed(array( - 'audio' => './for_the_demo/place_audio.png', - 'video' => './for_the_demo/place_video.png', - 'mediaplayer' => './for_the_demo/mediaplayer.swf', - 'altclass' => 'download' - )) . '</p>'; - - if ($enclosure->get_link() && $enclosure->get_type()) - { - echo '<p class="footnote" align="center">(' . $enclosure->get_type(); - if ($enclosure->get_size()) - { - echo '; ' . $enclosure->get_size() . ' MB'; - } - echo ')</p>'; - } - if ($enclosure->get_thumbnail()) - { - echo '<div><img src="' . $enclosure->get_thumbnail() . '" alt="" /></div>'; - } - echo '</div>'; - } - ?> - - <!-- Add links to add this post to one of a handful of services. --> - <p class="footnote favicons" align="center"> - <a href="<?php echo $item->add_to_blinklist(); ?>" title="Add post to Blinklist"><img src="./for_the_demo/favicons/blinklist.png" alt="Blinklist" /></a> - <a href="<?php echo $item->add_to_blogmarks(); ?>" title="Add post to Blogmarks"><img src="./for_the_demo/favicons/blogmarks.png" alt="Blogmarks" /></a> - <a href="<?php echo $item->add_to_delicious(); ?>" title="Add post to del.icio.us"><img src="./for_the_demo/favicons/delicious.png" alt="del.icio.us" /></a> - <a href="<?php echo $item->add_to_digg(); ?>" title="Digg this!"><img src="./for_the_demo/favicons/digg.png" alt="Digg" /></a> - <a href="<?php echo $item->add_to_magnolia(); ?>" title="Add post to Ma.gnolia"><img src="./for_the_demo/favicons/magnolia.png" alt="Ma.gnolia" /></a> - <a href="<?php echo $item->add_to_myweb20(); ?>" title="Add post to My Web 2.0"><img src="./for_the_demo/favicons/myweb2.png" alt="My Web 2.0" /></a> - <a href="<?php echo $item->add_to_newsvine(); ?>" title="Add post to Newsvine"><img src="./for_the_demo/favicons/newsvine.png" alt="Newsvine" /></a> - <a href="<?php echo $item->add_to_reddit(); ?>" title="Add post to Reddit"><img src="./for_the_demo/favicons/reddit.png" alt="Reddit" /></a> - <a href="<?php echo $item->add_to_segnalo(); ?>" title="Add post to Segnalo"><img src="./for_the_demo/favicons/segnalo.png" alt="Segnalo" /></a> - <a href="<?php echo $item->add_to_simpy(); ?>" title="Add post to Simpy"><img src="./for_the_demo/favicons/simpy.png" alt="Simpy" /></a> - <a href="<?php echo $item->add_to_spurl(); ?>" title="Add post to Spurl"><img src="./for_the_demo/favicons/spurl.png" alt="Spurl" /></a> - <a href="<?php echo $item->add_to_wists(); ?>" title="Add post to Wists"><img src="./for_the_demo/favicons/wists.png" alt="Wists" /></a> - <a href="<?php echo $item->search_technorati(); ?>" title="Who's linking to this post?"><img src="./for_the_demo/favicons/technorati.png" alt="Technorati" /></a> - </p> - - </div> - - <!-- Stop looping through each item once we've gone through all of them. --> - <?php endforeach; ?> - - <!-- From here on, we're no longer using data from the feed. --> - <?php endif; ?> - - </div> - - <div> - <!-- Display how fast the page was rendered. --> - <p class="footnote">Page processed in <?php $mtime = explode(' ', microtime()); echo round($mtime[0] + $mtime[1] - $starttime, 3); ?> seconds.</p> - - <!-- Display the version of SimplePie being loaded. --> - <p class="footnote">Powered by <a href="<?php echo SIMPLEPIE_URL; ?>"><?php echo SIMPLEPIE_NAME . ' ' . SIMPLEPIE_VERSION . ', Build ' . SIMPLEPIE_BUILD; ?></a>. Run the <a href="../compatibility_test/sp_compatibility_test.php">SimplePie Compatibility Test</a>. SimplePie is © 2004–<?php echo date('Y'); ?>, Ryan Parman and Geoffrey Sneddon, and licensed under the <a href="http://www.opensource.org/licenses/bsd-license.php">BSD License</a>.</p> - </div> - - </div> - -</div> - -</body> -</html> diff --git a/library/simplepie/demo/minimalistic.php b/library/simplepie/demo/minimalistic.php deleted file mode 100644 index 56509c00cb..0000000000 --- a/library/simplepie/demo/minimalistic.php +++ /dev/null @@ -1,137 +0,0 @@ -<?php - -function microtime_float() -{ - if (version_compare(phpversion(), '5.0.0', '>=')) - { - return microtime(true); - } - else - { - list($usec, $sec) = explode(' ', microtime()); - return ((float) $usec + (float) $sec); - } -} - -$start = microtime_float(); - -include('../simplepie.inc'); - -// Parse it -$feed = new SimplePie(); -if (!empty($_GET['feed'])) -{ - if (get_magic_quotes_gpc()) - { - $_GET['feed'] = stripslashes($_GET['feed']); - } - $feed->set_feed_url($_GET['feed']); - $feed->init(); -} -$feed->handle_content_type(); - -?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - -<html xmlns="http://www.w3.org/1999/xhtml"> -<head> -<title><?php echo (empty($_GET['feed'])) ? 'SimplePie' : 'SimplePie: ' . $feed->get_title(); ?></title> - -<!-- META HTTP-EQUIV --> -<meta http-equiv="content-type" content="text/html; charset=<?php echo ($feed->get_encoding()) ? $feed->get_encoding() : 'UTF-8'; ?>" /> -<meta http-equiv="imagetoolbar" content="false" /> - -<style type="text/css"> -html, body { - height:100%; - margin:0; - padding:0; -} - -h1 { - background-color:#333; - color:#fff; - font-size:3em; - margin:0; - padding:5px 15px; - text-align:center; -} - -div#footer { - padding:5px 0; -} - -div#footer, -div#footer a { - text-align:center; - font-size:0.7em; -} - -div#footer a { - text-decoration:underline; -} - -code { - background-color:#f3f3ff; - color:#000; -} - -pre { - background-color:#f3f3ff; - color:#000080; - border:1px dotted #000080; - padding:3px 5px; -} - -form { - margin:0; - padding:0; -} - -div.chunk { - border-bottom:1px solid #ccc; -} - -form#sp_form { - text-align:center; - margin:0; - padding:0; -} - -form#sp_form input.text { - width:85%; -} -</style> - -</head> - -<body> - <h1><?php echo (empty($_GET['feed'])) ? 'SimplePie' : 'SimplePie: ' . $feed->get_title(); ?></h1> - - <form action="" method="get" name="sp_form" id="sp_form"> - <p><input type="text" name="feed" value="<?php echo ($feed->subscribe_url()) ? htmlspecialchars($feed->subscribe_url()) : 'http://'; ?>" class="text" id="feed_input" /> <input type="submit" value="Read" class="button" /></p> - </form> - - <div id="sp_results"> - <?php if ($feed->data): ?> - <?php $items = $feed->get_items(); ?> - <p align="center"><span style="background-color:#ffc;">Displaying <?php echo $feed->get_item_quantity(); ?> most recent entries.</span></p> - <?php foreach($items as $item): ?> - <div class="chunk" style="padding:0 5px;"> - <h4><a href="<?php echo $item->get_permalink(); ?>"><?php echo $item->get_title(); ?></a> <?php echo $item->get_date('j M Y'); ?></h4> - <?php echo $item->get_content(); ?> - <?php - if ($enclosure = $item->get_enclosure(0)) - echo '<p><a href="' . $enclosure->get_link() . '" class="download"><img src="./for_the_demo/mini_podcast.png" alt="Podcast" title="Download the Podcast" border="0" /></a></p>'; - ?> - </div> - <?php endforeach; ?> - </div> - <?php endif; ?> - </div> - - <div id="footer"> - Powered by <?php echo SIMPLEPIE_LINKBACK; ?>, a product of <a href="http://www.skyzyx.com">Skyzyx Technologies</a>.<br /> - Page created in <?php echo round(microtime_float()-$start, 3); ?> seconds. - </div> -</body> -</html> diff --git a/library/simplepie/demo/multifeeds.php b/library/simplepie/demo/multifeeds.php deleted file mode 100644 index b23d792a2f..0000000000 --- a/library/simplepie/demo/multifeeds.php +++ /dev/null @@ -1,108 +0,0 @@ -<?php -/******************************************************************** -MULTIFEEDS TEST PAGE - -Nothing too exciting here. Just a sample page that demos integrated -Multifeeds support as well as cached favicons and perhaps a few other -things. - -Lots of this code is commented to help explain some of the new stuff. -Code was tested in PHP 5.2.2, but *should* also work with earlier -versions of PHP, as supported by SimplePie (PHP 4.1). - -********************************************************************/ - -// Include the SimplePie library, and the one that handles internationalized domain names. -require_once('../simplepie.inc'); -require_once('../idn/idna_convert.class.php'); - -// Initialize some feeds for use. -$feed = new SimplePie(); -$feed->set_feed_url(array( - 'http://rss.news.yahoo.com/rss/topstories', - 'http://news.google.com/?output=atom', - 'http://rss.cnn.com/rss/cnn_topstories.rss' -)); - -// When we set these, we need to make sure that the handler_image.php file is also trying to read from the same cache directory that we are. -$feed->set_favicon_handler('./handler_image.php'); -$feed->set_image_handler('./handler_image.php'); - -// Initialize the feed. -$feed->init(); - -// Make sure the page is being served with the UTF-8 headers. -$feed->handle_content_type(); - -// Begin the (X)HTML page. -?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <title>Multifeeds Test page</title> - <link rel="stylesheet" href="../demo/for_the_demo/simplepie.css" type="text/css" media="screen" title="SimplePie Styles" charset="utf-8" /> - <style type="text/css"> - div#site { - width:600px; - } - span.footnote { - white-space:nowrap; - } - h1 { - line-height:1.4em; - } - h4 { - padding-left:20px; - background-color:transparent; - background-repeat:no-repeat; - background-position:0 1px; - } - .clearBoth { - clear:both; - } - </style> -</head> -<body> -<div id="site"> - - <?php if ($feed->error): ?> - <p><?=$feed->error()?></p> - <?php endif ?> - - <div class="chunk"> - <h1>Quick-n-Dirty Multifeeds Demo</a></h1> - </div> - - <?php - // Let's loop through each item in the feed. - foreach($feed->get_items() as $item): - - // Let's give ourselves a reference to the parent $feed object for this particular item. - $feed = $item->get_feed(); - ?> - - <div class="chunk"> - <h4 style="background-image:url(<?php echo $feed->get_favicon(); ?>);"><a href="<?php echo $item->get_permalink(); ?>"><?php echo html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8'); ?></a></h4> - - <!-- get_content() prefers full content over summaries --> - <?php echo $item->get_content(); ?> - - <?php if ($enclosure = $item->get_enclosure()): ?> - <div> - <?php echo $enclosure->native_embed(array( - // New 'mediaplayer' attribute shows off Flash-based MP3 and FLV playback. - 'mediaplayer' => '../demo/for_the_demo/mediaplayer.swf' - )); ?> - </div> - <?php endif; ?> - - <p class="footnote">Source: <a href="<?php echo $feed->get_permalink(); ?>"><?php echo $feed->get_title(); ?></a> | <?php echo $item->get_date('j M Y | g:i a'); ?></p> - </div> - - <?php endforeach ?> - - <p class="footnote">This is a test of the emergency broadcast system. This is only a test… beeeeeeeeeeeeeeeeeeeeeeeeeep!</p> - -</div> -</body> -</html> \ No newline at end of file diff --git a/library/simplepie/demo/test.php b/library/simplepie/demo/test.php deleted file mode 100644 index 5b9943abbc..0000000000 --- a/library/simplepie/demo/test.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php -include_once('../simplepie.inc'); -include_once('../idn/idna_convert.class.php'); - -// Parse it -$feed = new SimplePie(); -if (isset($_GET['feed']) && $_GET['feed'] !== '') -{ - if (get_magic_quotes_gpc()) - { - $_GET['feed'] = stripslashes($_GET['feed']); - } - $feed->set_feed_url($_GET['feed']); - $feed->enable_cache(false); - $starttime = explode(' ', microtime()); - $starttime = $starttime[1] + $starttime[0]; - $feed->init(); - $endtime = explode(' ', microtime()); - $endtime = $endtime[1] + $endtime[0]; - $time = $endtime - $starttime; -} -else -{ - $time = 'null'; -} - -$feed->handle_content_type(); - -?> -<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"> -<title>SimplePie Test</title> -<pre> -<?php - -// memory_get_peak_usage() only exists on PHP 5.2 and higher if PHP is compiled with the --enable-memory-limit configuration option or on PHP 5.2.1 and higher (which runs as if --enable-memory-limit was on, with no option) -if (function_exists('memory_get_peak_usage')) -{ - var_dump($time, memory_get_usage(), memory_get_peak_usage()); -} -// memory_get_usage() only exists if PHP is compiled with the --enable-memory-limit configuration option or on PHP 5.2.1 and higher (which runs as if --enable-memory-limit was on, with no option) -else if (function_exists('memory_get_usage')) -{ - var_dump($time, memory_get_usage()); -} -else -{ - var_dump($time); -} - -// Output buffer -function callable_htmlspecialchars($string) -{ - return htmlspecialchars($string); -} -ob_start('callable_htmlspecialchars'); - -// Output -print_r($feed); -ob_end_flush(); - -?> -</pre> \ No newline at end of file diff --git a/library/simplepie/idn/LICENCE b/library/simplepie/idn/LICENCE deleted file mode 100644 index 25a1d22dfe..0000000000 --- a/library/simplepie/idn/LICENCE +++ /dev/null @@ -1,502 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - <one line to give the library's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - <signature of Ty Coon>, 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/library/simplepie/idn/ReadMe.txt b/library/simplepie/idn/ReadMe.txt deleted file mode 100644 index 7ca8c7e6de..0000000000 --- a/library/simplepie/idn/ReadMe.txt +++ /dev/null @@ -1,123 +0,0 @@ -******************************************************************************* -* * -* IDNA Convert (idna_convert.class.php) * -* * -* http://idnaconv.phlymail.de mailto:phlymail@phlylabs.de * -******************************************************************************* -* (c) 2004-2007 phlyLabs, Berlin * -* This file is encoded in UTF-8 * -******************************************************************************* - -Introduction ------------- - -The class idna_convert allows to convert internationalized domain names -(see RFC 3490, 3491, 3492 and 3454 for detials) as they can be used with various -registries worldwide to be translated between their original (localized) form -and their encoded form as it will be used in the DNS (Domain Name System). - -The class provides two public methods, encode() and decode(), which do exactly -what you would expect them to do. You are allowed to use complete domain names, -simple strings and complete email addresses as well. That means, that you might -use any of the following notations: - -- www.nörgler.com -- xn--nrgler-wxa -- xn--brse-5qa.xn--knrz-1ra.info - -Errors, incorrectly encoded or invalid strings will lead to either a FALSE -response (when in strict mode) or to only partially converted strings. -You can query the occured error by calling the method get_last_error(). - -Unicode strings are expected to be either UTF-8 strings, UCS-4 strings or UCS-4 -arrays. The default format is UTF-8. For setting different encodings, you can -call the method setParams() - please see the inline documentation for details. -ACE strings (the Punycode form) are always 7bit ASCII strings. - -ATTENTION: We no longer supply the PHP5 version of the class. It is not -necessary for achieving a successfull conversion, since the supplied PHP code is -compatible with both PHP4 and PHP5. We expect to see no compatibility issues -with the upcoming PHP6, too. - - -Files ------ - -idna_convert.class.php - The actual class -idna_convert.create.npdata.php - Useful for (re)creating the NPData file -npdata.ser - Serialized data for NamePrep -example.php - An example web page for converting -ReadMe.txt - This file -LICENCE - The LGPL licence file - -The class is contained in idna_convert.class.php. -MAKE SURE to copy the npdata.ser file into the same folder as the class file -itself! - - -Examples --------- - -1. Say we wish to encode the domain name nörgler.com: - -// Include the class -include_once('idna_convert.class.php'); -// Instantiate it * -$IDN = new idna_convert(); -// The input string, if input is not UTF-8 or UCS-4, it must be converted before -$input = utf8_encode('nörgler.com'); -// Encode it to its punycode presentation -$output = $IDN->encode($input); -// Output, what we got now -echo $output; // This will read: xn--nrgler-wxa.com - - -2. We received an email from a punycoded domain and are willing to learn, how - the domain name reads originally - -// Include the class -include_once('idna_convert.class.php'); -// Instantiate it (depending on the version you are using) with -$IDN = new idna_convert(); -// The input string -$input = 'andre@xn--brse-5qa.xn--knrz-1ra.info'; -// Encode it to its punycode presentation -$output = $IDN->decode($input); -// Output, what we got now, if output should be in a format different to UTF-8 -// or UCS-4, you will have to convert it before outputting it -echo utf8_decode($output); // This will read: andre@börse.knörz.info - - -3. The input is read from a UCS-4 coded file and encoded line by line. By - appending the optional second parameter we tell enode() about the input - format to be used - -// Include the class -include_once('idna_convert.class.php'); -// Instantiate it -$IDN = new dinca_convert(); -// Iterate through the input file line by line -foreach (file('ucs4-domains.txt') as $line) { - echo $IDN->encode(trim($line), 'ucs4_string'); - echo "\n"; -} - - -NPData ------- - -Should you need to recreate the npdata.ser file, which holds all necessary translation -tables in a serialized format, you can run the file idna_convert.create.npdata.php, which -creates the file for you and stores it in the same folder, where it is placed. -Should you need to do changes to the tables you can do so, but beware of the consequences. - - -Contact us ----------- - -In case of errors, bugs, questions, wishes, please don't hesitate to contact us -under the email address above. - -The team of phlyLabs -http://phlylabs.de -mailto:phlymail@phlylabs.de \ No newline at end of file diff --git a/library/simplepie/idn/idna_convert.class.php b/library/simplepie/idn/idna_convert.class.php deleted file mode 100644 index ed2bae26db..0000000000 --- a/library/simplepie/idn/idna_convert.class.php +++ /dev/null @@ -1,969 +0,0 @@ -<?php -// {{{ license - -/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */ -// -// +----------------------------------------------------------------------+ -// | This library is free software; you can redistribute it and/or modify | -// | it under the terms of the GNU Lesser General Public License as | -// | published by the Free Software Foundation; either version 2.1 of the | -// | License, or (at your option) any later version. | -// | | -// | This library is distributed in the hope that it will be useful, but | -// | WITHOUT ANY WARRANTY; without even the implied warranty of | -// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | -// | Lesser General Public License for more details. | -// | | -// | You should have received a copy of the GNU Lesser General Public | -// | License along with this library; if not, write to the Free Software | -// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | -// | USA. | -// +----------------------------------------------------------------------+ -// - -// }}} - -/** - * Encode/decode Internationalized Domain Names. - * - * The class allows to convert internationalized domain names - * (see RFC 3490 for details) as they can be used with various registries worldwide - * to be translated between their original (localized) form and their encoded form - * as it will be used in the DNS (Domain Name System). - * - * The class provides two public methods, encode() and decode(), which do exactly - * what you would expect them to do. You are allowed to use complete domain names, - * simple strings and complete email addresses as well. That means, that you might - * use any of the following notations: - * - * - www.nörgler.com - * - xn--nrgler-wxa - * - xn--brse-5qa.xn--knrz-1ra.info - * - * Unicode input might be given as either UTF-8 string, UCS-4 string or UCS-4 - * array. Unicode output is available in the same formats. - * You can select your preferred format via {@link set_paramter()}. - * - * ACE input and output is always expected to be ASCII. - * - * @author Matthias Sommerfeld <mso@phlylabs.de> - * @copyright 2004-2007 phlyLabs Berlin, http://phlylabs.de - * @version 0.5.1 - * - */ -class idna_convert -{ - /** - * Holds all relevant mapping tables, loaded from a seperate file on construct - * See RFC3454 for details - * - * @var array - * @access private - */ - var $NP = array(); - - // Internal settings, do not mess with them - var $_punycode_prefix = 'xn--'; - var $_invalid_ucs = 0x80000000; - var $_max_ucs = 0x10FFFF; - var $_base = 36; - var $_tmin = 1; - var $_tmax = 26; - var $_skew = 38; - var $_damp = 700; - var $_initial_bias = 72; - var $_initial_n = 0x80; - var $_sbase = 0xAC00; - var $_lbase = 0x1100; - var $_vbase = 0x1161; - var $_tbase = 0x11A7; - var $_lcount = 19; - var $_vcount = 21; - var $_tcount = 28; - var $_ncount = 588; // _vcount * _tcount - var $_scount = 11172; // _lcount * _tcount * _vcount - var $_error = false; - - // See {@link set_paramter()} for details of how to change the following - // settings from within your script / application - var $_api_encoding = 'utf8'; // Default input charset is UTF-8 - var $_allow_overlong = false; // Overlong UTF-8 encodings are forbidden - var $_strict_mode = false; // Behave strict or not - - // The constructor - function idna_convert($options = false) - { - $this->slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount; - if (function_exists('file_get_contents')) { - $this->NP = unserialize(file_get_contents(dirname(__FILE__).'/npdata.ser')); - } else { - $this->NP = unserialize(join('', file(dirname(__FILE__).'/npdata.ser'))); - } - // If parameters are given, pass these to the respective method - if (is_array($options)) { - return $this->set_parameter($options); - } - return true; - } - - /** - * Sets a new option value. Available options and values: - * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8, - * 'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8] - * [overlong - Unicode does not allow unnecessarily long encodings of chars, - * to allow this, set this parameter to true, else to false; - * default is false.] - * [strict - true: strict mode, good for registration purposes - Causes errors - * on failures; false: loose mode, ideal for "wildlife" applications - * by silently ignoring errors and returning the original input instead - * - * @param mixed Parameter to set (string: single parameter; array of Parameter => Value pairs) - * @param string Value to use (if parameter 1 is a string) - * @return boolean true on success, false otherwise - * @access public - */ - function set_parameter($option, $value = false) - { - if (!is_array($option)) { - $option = array($option => $value); - } - foreach ($option as $k => $v) { - switch ($k) { - case 'encoding': - switch ($v) { - case 'utf8': - case 'ucs4_string': - case 'ucs4_array': - $this->_api_encoding = $v; - break; - default: - $this->_error('Set Parameter: Unknown parameter '.$v.' for option '.$k); - return false; - } - break; - case 'overlong': - $this->_allow_overlong = ($v) ? true : false; - break; - case 'strict': - $this->_strict_mode = ($v) ? true : false; - break; - default: - $this->_error('Set Parameter: Unknown option '.$k); - return false; - } - } - return true; - } - - /** - * Decode a given ACE domain name - * @param string Domain name (ACE string) - * [@param string Desired output encoding, see {@link set_parameter}] - * @return string Decoded Domain name (UTF-8 or UCS-4) - * @access public - */ - function decode($input, $one_time_encoding = false) - { - // Optionally set - if ($one_time_encoding) { - switch ($one_time_encoding) { - case 'utf8': - case 'ucs4_string': - case 'ucs4_array': - break; - default: - $this->_error('Unknown encoding '.$one_time_encoding); - return false; - } - } - // Make sure to drop any newline characters around - $input = trim($input); - - // Negotiate input and try to determine, whether it is a plain string, - // an email address or something like a complete URL - if (strpos($input, '@')) { // Maybe it is an email address - // No no in strict mode - if ($this->_strict_mode) { - $this->_error('Only simple domain name parts can be handled in strict mode'); - return false; - } - list ($email_pref, $input) = explode('@', $input, 2); - $arr = explode('.', $input); - foreach ($arr as $k => $v) { - if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) { - $conv = $this->_decode($v); - if ($conv) $arr[$k] = $conv; - } - } - $input = join('.', $arr); - $arr = explode('.', $email_pref); - foreach ($arr as $k => $v) { - if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) { - $conv = $this->_decode($v); - if ($conv) $arr[$k] = $conv; - } - } - $email_pref = join('.', $arr); - $return = $email_pref . '@' . $input; - } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters) - // No no in strict mode - if ($this->_strict_mode) { - $this->_error('Only simple domain name parts can be handled in strict mode'); - return false; - } - $parsed = parse_url($input); - if (isset($parsed['host'])) { - $arr = explode('.', $parsed['host']); - foreach ($arr as $k => $v) { - $conv = $this->_decode($v); - if ($conv) $arr[$k] = $conv; - } - $parsed['host'] = join('.', $arr); - $return = - (empty($parsed['scheme']) ? '' : $parsed['scheme'].(strtolower($parsed['scheme']) == 'mailto' ? ':' : '://')) - .(empty($parsed['user']) ? '' : $parsed['user'].(empty($parsed['pass']) ? '' : ':'.$parsed['pass']).'@') - .$parsed['host'] - .(empty($parsed['port']) ? '' : ':'.$parsed['port']) - .(empty($parsed['path']) ? '' : $parsed['path']) - .(empty($parsed['query']) ? '' : '?'.$parsed['query']) - .(empty($parsed['fragment']) ? '' : '#'.$parsed['fragment']); - } else { // parse_url seems to have failed, try without it - $arr = explode('.', $input); - foreach ($arr as $k => $v) { - $conv = $this->_decode($v); - $arr[$k] = ($conv) ? $conv : $v; - } - $return = join('.', $arr); - } - } else { // Otherwise we consider it being a pure domain name string - $return = $this->_decode($input); - if (!$return) $return = $input; - } - // The output is UTF-8 by default, other output formats need conversion here - // If one time encoding is given, use this, else the objects property - switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) { - case 'utf8': - return $return; - break; - case 'ucs4_string': - return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return)); - break; - case 'ucs4_array': - return $this->_utf8_to_ucs4($return); - break; - default: - $this->_error('Unsupported output format'); - return false; - } - } - - /** - * Encode a given UTF-8 domain name - * @param string Domain name (UTF-8 or UCS-4) - * [@param string Desired input encoding, see {@link set_parameter}] - * @return string Encoded Domain name (ACE string) - * @access public - */ - function encode($decoded, $one_time_encoding = false) - { - // Forcing conversion of input to UCS4 array - // If one time encoding is given, use this, else the objects property - switch ($one_time_encoding ? $one_time_encoding : $this->_api_encoding) { - case 'utf8': - $decoded = $this->_utf8_to_ucs4($decoded); - break; - case 'ucs4_string': - $decoded = $this->_ucs4_string_to_ucs4($decoded); - case 'ucs4_array': - break; - default: - $this->_error('Unsupported input format: '.($one_time_encoding ? $one_time_encoding : $this->_api_encoding)); - return false; - } - - // No input, no output, what else did you expect? - if (empty($decoded)) return ''; - - // Anchors for iteration - $last_begin = 0; - // Output string - $output = ''; - foreach ($decoded as $k => $v) { - // Make sure to use just the plain dot - switch($v) { - case 0x3002: - case 0xFF0E: - case 0xFF61: - $decoded[$k] = 0x2E; - // Right, no break here, the above are converted to dots anyway - // Stumbling across an anchoring character - case 0x2E: - case 0x2F: - case 0x3A: - case 0x3F: - case 0x40: - // Neither email addresses nor URLs allowed in strict mode - if ($this->_strict_mode) { - $this->_error('Neither email addresses nor URLs are allowed in strict mode.'); - return false; - } else { - // Skip first char - if ($k) { - $encoded = ''; - $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin))); - if ($encoded) { - $output .= $encoded; - } else { - $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin))); - } - $output .= chr($decoded[$k]); - } - $last_begin = $k + 1; - } - } - } - // Catch the rest of the string - if ($last_begin) { - $inp_len = sizeof($decoded); - $encoded = ''; - $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin))); - if ($encoded) { - $output .= $encoded; - } else { - $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin))); - } - return $output; - } else { - if ($output = $this->_encode($decoded)) { - return $output; - } else { - return $this->_ucs4_to_utf8($decoded); - } - } - } - - /** - * Use this method to get the last error ocurred - * @param void - * @return string The last error, that occured - * @access public - */ - function get_last_error() - { - return $this->_error; - } - - /** - * The actual decoding algorithm - * @access private - */ - function _decode($encoded) - { - // We do need to find the Punycode prefix - if (!preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $encoded)) { - $this->_error('This is not a punycode string'); - return false; - } - $encode_test = preg_replace('!^'.preg_quote($this->_punycode_prefix, '!').'!', '', $encoded); - // If nothing left after removing the prefix, it is hopeless - if (!$encode_test) { - $this->_error('The given encoded string was empty'); - return false; - } - // Find last occurence of the delimiter - $delim_pos = strrpos($encoded, '-'); - if ($delim_pos > strlen($this->_punycode_prefix)) { - for ($k = strlen($this->_punycode_prefix); $k < $delim_pos; ++$k) { - $decoded[] = ord($encoded{$k}); - } - } else { - $decoded = array(); - } - $deco_len = count($decoded); - $enco_len = strlen($encoded); - - // Wandering through the strings; init - $is_first = true; - $bias = $this->_initial_bias; - $idx = 0; - $char = $this->_initial_n; - - for ($enco_idx = ($delim_pos) ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) { - for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) { - $digit = $this->_decode_digit($encoded{$enco_idx++}); - $idx += $digit * $w; - $t = ($k <= $bias) ? $this->_tmin : - (($k >= $bias + $this->_tmax) ? $this->_tmax : ($k - $bias)); - if ($digit < $t) break; - $w = (int) ($w * ($this->_base - $t)); - } - $bias = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first); - $is_first = false; - $char += (int) ($idx / ($deco_len + 1)); - $idx %= ($deco_len + 1); - if ($deco_len > 0) { - // Make room for the decoded char - for ($i = $deco_len; $i > $idx; $i--) { - $decoded[$i] = $decoded[($i - 1)]; - } - } - $decoded[$idx++] = $char; - } - return $this->_ucs4_to_utf8($decoded); - } - - /** - * The actual encoding algorithm - * @access private - */ - function _encode($decoded) - { - // We cannot encode a domain name containing the Punycode prefix - $extract = strlen($this->_punycode_prefix); - $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix); - $check_deco = array_slice($decoded, 0, $extract); - - if ($check_pref == $check_deco) { - $this->_error('This is already a punycode string'); - return false; - } - // We will not try to encode strings consisting of basic code points only - $encodable = false; - foreach ($decoded as $k => $v) { - if ($v > 0x7a) { - $encodable = true; - break; - } - } - if (!$encodable) { - $this->_error('The given string does not contain encodable chars'); - return false; - } - - // Do NAMEPREP - $decoded = $this->_nameprep($decoded); - if (!$decoded || !is_array($decoded)) return false; // NAMEPREP failed - - $deco_len = count($decoded); - if (!$deco_len) return false; // Empty array - - $codecount = 0; // How many chars have been consumed - - $encoded = ''; - // Copy all basic code points to output - for ($i = 0; $i < $deco_len; ++$i) { - $test = $decoded[$i]; - // Will match [-0-9a-zA-Z] - if ((0x2F < $test && $test < 0x40) || (0x40 < $test && $test < 0x5B) - || (0x60 < $test && $test <= 0x7B) || (0x2D == $test)) { - $encoded .= chr($decoded[$i]); - $codecount++; - } - } - if ($codecount == $deco_len) return $encoded; // All codepoints were basic ones - - // Start with the prefix; copy it to output - $encoded = $this->_punycode_prefix.$encoded; - - // If we have basic code points in output, add an hyphen to the end - if ($codecount) $encoded .= '-'; - - // Now find and encode all non-basic code points - $is_first = true; - $cur_code = $this->_initial_n; - $bias = $this->_initial_bias; - $delta = 0; - while ($codecount < $deco_len) { - // Find the smallest code point >= the current code point and - // remember the last ouccrence of it in the input - for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) { - if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) { - $next_code = $decoded[$i]; - } - } - - $delta += ($next_code - $cur_code) * ($codecount + 1); - $cur_code = $next_code; - - // Scan input again and encode all characters whose code point is $cur_code - for ($i = 0; $i < $deco_len; $i++) { - if ($decoded[$i] < $cur_code) { - $delta++; - } elseif ($decoded[$i] == $cur_code) { - for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) { - $t = ($k <= $bias) ? $this->_tmin : - (($k >= $bias + $this->_tmax) ? $this->_tmax : $k - $bias); - if ($q < $t) break; - $encoded .= $this->_encode_digit(intval($t + (($q - $t) % ($this->_base - $t)))); //v0.4.5 Changed from ceil() to intval() - $q = (int) (($q - $t) / ($this->_base - $t)); - } - $encoded .= $this->_encode_digit($q); - $bias = $this->_adapt($delta, $codecount+1, $is_first); - $codecount++; - $delta = 0; - $is_first = false; - } - } - $delta++; - $cur_code++; - } - return $encoded; - } - - /** - * Adapt the bias according to the current code point and position - * @access private - */ - function _adapt($delta, $npoints, $is_first) - { - $delta = intval($is_first ? ($delta / $this->_damp) : ($delta / 2)); - $delta += intval($delta / $npoints); - for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) { - $delta = intval($delta / ($this->_base - $this->_tmin)); - } - return intval($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew)); - } - - /** - * Encoding a certain digit - * @access private - */ - function _encode_digit($d) - { - return chr($d + 22 + 75 * ($d < 26)); - } - - /** - * Decode a certain digit - * @access private - */ - function _decode_digit($cp) - { - $cp = ord($cp); - return ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $this->_base)); - } - - /** - * Internal error handling method - * @access private - */ - function _error($error = '') - { - $this->_error = $error; - } - - /** - * Do Nameprep according to RFC3491 and RFC3454 - * @param array Unicode Characters - * @return string Unicode Characters, Nameprep'd - * @access private - */ - function _nameprep($input) - { - $output = array(); - $error = false; - // - // Mapping - // Walking through the input array, performing the required steps on each of - // the input chars and putting the result into the output array - // While mapping required chars we apply the cannonical ordering - foreach ($input as $v) { - // Map to nothing == skip that code point - if (in_array($v, $this->NP['map_nothing'])) continue; - - // Try to find prohibited input - if (in_array($v, $this->NP['prohibit']) || in_array($v, $this->NP['general_prohibited'])) { - $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v)); - return false; - } - foreach ($this->NP['prohibit_ranges'] as $range) { - if ($range[0] <= $v && $v <= $range[1]) { - $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v)); - return false; - } - } - // - // Hangul syllable decomposition - if (0xAC00 <= $v && $v <= 0xD7AF) { - foreach ($this->_hangul_decompose($v) as $out) { - $output[] = (int) $out; - } - // There's a decomposition mapping for that code point - } elseif (isset($this->NP['replacemaps'][$v])) { - foreach ($this->_apply_cannonical_ordering($this->NP['replacemaps'][$v]) as $out) { - $output[] = (int) $out; - } - } else { - $output[] = (int) $v; - } - } - // Before applying any Combining, try to rearrange any Hangul syllables - $output = $this->_hangul_compose($output); - // - // Combine code points - // - $last_class = 0; - $last_starter = 0; - $out_len = count($output); - for ($i = 0; $i < $out_len; ++$i) { - $class = $this->_get_combining_class($output[$i]); - if ((!$last_class || $last_class > $class) && $class) { - // Try to match - $seq_len = $i - $last_starter; - $out = $this->_combine(array_slice($output, $last_starter, $seq_len)); - // On match: Replace the last starter with the composed character and remove - // the now redundant non-starter(s) - if ($out) { - $output[$last_starter] = $out; - if (count($out) != $seq_len) { - for ($j = $i+1; $j < $out_len; ++$j) { - $output[$j-1] = $output[$j]; - } - unset($output[$out_len]); - } - // Rewind the for loop by one, since there can be more possible compositions - $i--; - $out_len--; - $last_class = ($i == $last_starter) ? 0 : $this->_get_combining_class($output[$i-1]); - continue; - } - } - // The current class is 0 - if (!$class) $last_starter = $i; - $last_class = $class; - } - return $output; - } - - /** - * Decomposes a Hangul syllable - * (see http://www.unicode.org/unicode/reports/tr15/#Hangul - * @param integer 32bit UCS4 code point - * @return array Either Hangul Syllable decomposed or original 32bit value as one value array - * @access private - */ - function _hangul_decompose($char) - { - $sindex = (int) $char - $this->_sbase; - if ($sindex < 0 || $sindex >= $this->_scount) { - return array($char); - } - $result = array(); - $result[] = (int) $this->_lbase + $sindex / $this->_ncount; - $result[] = (int) $this->_vbase + ($sindex % $this->_ncount) / $this->_tcount; - $T = intval($this->_tbase + $sindex % $this->_tcount); - if ($T != $this->_tbase) $result[] = $T; - return $result; - } - /** - * Ccomposes a Hangul syllable - * (see http://www.unicode.org/unicode/reports/tr15/#Hangul - * @param array Decomposed UCS4 sequence - * @return array UCS4 sequence with syllables composed - * @access private - */ - function _hangul_compose($input) - { - $inp_len = count($input); - if (!$inp_len) return array(); - $result = array(); - $last = (int) $input[0]; - $result[] = $last; // copy first char from input to output - - for ($i = 1; $i < $inp_len; ++$i) { - $char = (int) $input[$i]; - $sindex = $last - $this->_sbase; - $lindex = $last - $this->_lbase; - $vindex = $char - $this->_vbase; - $tindex = $char - $this->_tbase; - // Find out, whether two current characters are LV and T - if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount == 0) - && 0 <= $tindex && $tindex <= $this->_tcount) { - // create syllable of form LVT - $last += $tindex; - $result[(count($result) - 1)] = $last; // reset last - continue; // discard char - } - // Find out, whether two current characters form L and V - if (0 <= $lindex && $lindex < $this->_lcount && 0 <= $vindex && $vindex < $this->_vcount) { - // create syllable of form LV - $last = (int) $this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount; - $result[(count($result) - 1)] = $last; // reset last - continue; // discard char - } - // if neither case was true, just add the character - $last = $char; - $result[] = $char; - } - return $result; - } - - /** - * Returns the combining class of a certain wide char - * @param integer Wide char to check (32bit integer) - * @return integer Combining class if found, else 0 - * @access private - */ - function _get_combining_class($char) - { - return isset($this->NP['norm_combcls'][$char]) ? $this->NP['norm_combcls'][$char] : 0; - } - - /** - * Apllies the cannonical ordering of a decomposed UCS4 sequence - * @param array Decomposed UCS4 sequence - * @return array Ordered USC4 sequence - * @access private - */ - function _apply_cannonical_ordering($input) - { - $swap = true; - $size = count($input); - while ($swap) { - $swap = false; - $last = $this->_get_combining_class(intval($input[0])); - for ($i = 0; $i < $size-1; ++$i) { - $next = $this->_get_combining_class(intval($input[$i+1])); - if ($next != 0 && $last > $next) { - // Move item leftward until it fits - for ($j = $i + 1; $j > 0; --$j) { - if ($this->_get_combining_class(intval($input[$j-1])) <= $next) break; - $t = intval($input[$j]); - $input[$j] = intval($input[$j-1]); - $input[$j-1] = $t; - $swap = true; - } - // Reentering the loop looking at the old character again - $next = $last; - } - $last = $next; - } - } - return $input; - } - - /** - * Do composition of a sequence of starter and non-starter - * @param array UCS4 Decomposed sequence - * @return array Ordered USC4 sequence - * @access private - */ - function _combine($input) - { - $inp_len = count($input); - foreach ($this->NP['replacemaps'] as $np_src => $np_target) { - if ($np_target[0] != $input[0]) continue; - if (count($np_target) != $inp_len) continue; - $hit = false; - foreach ($input as $k2 => $v2) { - if ($v2 == $np_target[$k2]) { - $hit = true; - } else { - $hit = false; - break; - } - } - if ($hit) return $np_src; - } - return false; - } - - /** - * This converts an UTF-8 encoded string to its UCS-4 representation - * By talking about UCS-4 "strings" we mean arrays of 32bit integers representing - * each of the "chars". This is due to PHP not being able to handle strings with - * bit depth different from 8. This apllies to the reverse method _ucs4_to_utf8(), too. - * The following UTF-8 encodings are supported: - * bytes bits representation - * 1 7 0xxxxxxx - * 2 11 110xxxxx 10xxxxxx - * 3 16 1110xxxx 10xxxxxx 10xxxxxx - * 4 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - * 5 26 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - * 6 31 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - * Each x represents a bit that can be used to store character data. - * The five and six byte sequences are part of Annex D of ISO/IEC 10646-1:2000 - * @access private - */ - function _utf8_to_ucs4($input) - { - $output = array(); - $out_len = 0; - $inp_len = strlen($input); - $mode = 'next'; - $test = 'none'; - for ($k = 0; $k < $inp_len; ++$k) { - $v = ord($input{$k}); // Extract byte from input string - - if ($v < 128) { // We found an ASCII char - put into stirng as is - $output[$out_len] = $v; - ++$out_len; - if ('add' == $mode) { - $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k); - return false; - } - continue; - } - if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char - $start_byte = $v; - $mode = 'add'; - $test = 'range'; - if ($v >> 5 == 6) { // &110xxxxx 10xxxxx - $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left - $v = ($v - 192) << 6; - } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx - $next_byte = 1; - $v = ($v - 224) << 12; - } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - $next_byte = 2; - $v = ($v - 240) << 18; - } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - $next_byte = 3; - $v = ($v - 248) << 24; - } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - $next_byte = 4; - $v = ($v - 252) << 30; - } else { - $this->_error('This might be UTF-8, but I don\'t understand it at byte '.$k); - return false; - } - if ('add' == $mode) { - $output[$out_len] = (int) $v; - ++$out_len; - continue; - } - } - if ('add' == $mode) { - if (!$this->_allow_overlong && $test == 'range') { - $test = 'none'; - if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) { - $this->_error('Bogus UTF-8 character detected (out of legal range) at byte '.$k); - return false; - } - } - if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx - $v = ($v - 128) << ($next_byte * 6); - $output[($out_len - 1)] += $v; - --$next_byte; - } else { - $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k); - return false; - } - if ($next_byte < 0) { - $mode = 'next'; - } - } - } // for - return $output; - } - - /** - * Convert UCS-4 string into UTF-8 string - * See _utf8_to_ucs4() for details - * @access private - */ - function _ucs4_to_utf8($input) - { - $output = ''; - $k = 0; - foreach ($input as $v) { - ++$k; - // $v = ord($v); - if ($v < 128) { // 7bit are transferred literally - $output .= chr($v); - } elseif ($v < (1 << 11)) { // 2 bytes - $output .= chr(192 + ($v >> 6)) . chr(128 + ($v & 63)); - } elseif ($v < (1 << 16)) { // 3 bytes - $output .= chr(224 + ($v >> 12)) . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63)); - } elseif ($v < (1 << 21)) { // 4 bytes - $output .= chr(240 + ($v >> 18)) . chr(128 + (($v >> 12) & 63)) - . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63)); - } elseif ($v < (1 << 26)) { // 5 bytes - $output .= chr(248 + ($v >> 24)) . chr(128 + (($v >> 18) & 63)) - . chr(128 + (($v >> 12) & 63)) . chr(128 + (($v >> 6) & 63)) - . chr(128 + ($v & 63)); - } elseif ($v < (1 << 31)) { // 6 bytes - $output .= chr(252 + ($v >> 30)) . chr(128 + (($v >> 24) & 63)) - . chr(128 + (($v >> 18) & 63)) . chr(128 + (($v >> 12) & 63)) - . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63)); - } else { - $this->_error('Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k); - return false; - } - } - return $output; - } - - /** - * Convert UCS-4 array into UCS-4 string - * - * @access private - */ - function _ucs4_to_ucs4_string($input) - { - $output = ''; - // Take array values and split output to 4 bytes per value - // The bit mask is 255, which reads &11111111 - foreach ($input as $v) { - $output .= chr(($v >> 24) & 255).chr(($v >> 16) & 255).chr(($v >> 8) & 255).chr($v & 255); - } - return $output; - } - - /** - * Convert UCS-4 strin into UCS-4 garray - * - * @access private - */ - function _ucs4_string_to_ucs4($input) - { - $output = array(); - $inp_len = strlen($input); - // Input length must be dividable by 4 - if ($inp_len % 4) { - $this->_error('Input UCS4 string is broken'); - return false; - } - // Empty input - return empty output - if (!$inp_len) return $output; - for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) { - // Increment output position every 4 input bytes - if (!($i % 4)) { - $out_len++; - $output[$out_len] = 0; - } - $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) ); - } - return $output; - } -} - -/** -* Adapter class for aligning the API of idna_convert with that of Net_IDNA -* @author Matthias Sommerfeld <mso@phlylabs.de> -*/ -class Net_IDNA_php4 extends idna_convert -{ - /** - * Sets a new option value. Available options and values: - * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8, - * 'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8] - * [overlong - Unicode does not allow unnecessarily long encodings of chars, - * to allow this, set this parameter to true, else to false; - * default is false.] - * [strict - true: strict mode, good for registration purposes - Causes errors - * on failures; false: loose mode, ideal for "wildlife" applications - * by silently ignoring errors and returning the original input instead - * - * @param mixed Parameter to set (string: single parameter; array of Parameter => Value pairs) - * @param string Value to use (if parameter 1 is a string) - * @return boolean true on success, false otherwise - * @access public - */ - function setParams($option, $param = false) - { - return $this->IC->set_parameters($option, $param); - } -} - -?> \ No newline at end of file diff --git a/library/simplepie/idn/npdata.ser b/library/simplepie/idn/npdata.ser deleted file mode 100644 index d7ce6d03f2..0000000000 --- a/library/simplepie/idn/npdata.ser +++ /dev/null @@ -1 +0,0 @@ -a:6:{s:11:"map_nothing";a:27:{i:0;i:173;i:1;i:847;i:2;i:6150;i:3;i:6155;i:4;i:6156;i:5;i:6157;i:6;i:8203;i:7;i:8204;i:8;i:8205;i:9;i:8288;i:10;i:65024;i:11;i:65025;i:12;i:65026;i:13;i:65027;i:14;i:65028;i:15;i:65029;i:16;i:65030;i:17;i:65031;i:18;i:65032;i:19;i:65033;i:20;i:65034;i:21;i:65035;i:22;i:65036;i:23;i:65037;i:24;i:65038;i:25;i:65039;i:26;i:65279;}s:18:"general_prohibited";a:64:{i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;i:11;i:11;i:12;i:12;i:13;i:13;i:14;i:14;i:15;i:15;i:16;i:16;i:17;i:17;i:18;i:18;i:19;i:19;i:20;i:20;i:21;i:21;i:22;i:22;i:23;i:23;i:24;i:24;i:25;i:25;i:26;i:26;i:27;i:27;i:28;i:28;i:29;i:29;i:30;i:30;i:31;i:31;i:32;i:32;i:33;i:33;i:34;i:34;i:35;i:35;i:36;i:36;i:37;i:37;i:38;i:38;i:39;i:39;i:40;i:40;i:41;i:41;i:42;i:42;i:43;i:43;i:44;i:44;i:45;i:47;i:46;i:59;i:47;i:60;i:48;i:61;i:49;i:62;i:50;i:63;i:51;i:64;i:52;i:91;i:53;i:92;i:54;i:93;i:55;i:94;i:56;i:95;i:57;i:96;i:58;i:123;i:59;i:124;i:60;i:125;i:61;i:126;i:62;i:127;i:63;i:12290;}s:8:"prohibit";a:84:{i:0;i:160;i:1;i:5760;i:2;i:8192;i:3;i:8193;i:4;i:8194;i:5;i:8195;i:6;i:8196;i:7;i:8197;i:8;i:8198;i:9;i:8199;i:10;i:8200;i:11;i:8201;i:12;i:8202;i:13;i:8203;i:14;i:8239;i:15;i:8287;i:16;i:12288;i:17;i:1757;i:18;i:1807;i:19;i:6158;i:20;i:8204;i:21;i:8205;i:22;i:8232;i:23;i:8233;i:24;i:65279;i:25;i:65529;i:26;i:65530;i:27;i:65531;i:28;i:65532;i:29;i:65534;i:30;i:65535;i:31;i:131070;i:32;i:131071;i:33;i:196606;i:34;i:196607;i:35;i:262142;i:36;i:262143;i:37;i:327678;i:38;i:327679;i:39;i:393214;i:40;i:393215;i:41;i:458750;i:42;i:458751;i:43;i:524286;i:44;i:524287;i:45;i:589822;i:46;i:589823;i:47;i:655358;i:48;i:655359;i:49;i:720894;i:50;i:720895;i:51;i:786430;i:52;i:786431;i:53;i:851966;i:54;i:851967;i:55;i:917502;i:56;i:917503;i:57;i:983038;i:58;i:983039;i:59;i:1048574;i:60;i:1048575;i:61;i:1114110;i:62;i:1114111;i:63;i:65529;i:64;i:65530;i:65;i:65531;i:66;i:65532;i:67;i:65533;i:68;i:832;i:69;i:833;i:70;i:8206;i:71;i:8207;i:72;i:8234;i:73;i:8235;i:74;i:8236;i:75;i:8237;i:76;i:8238;i:77;i:8298;i:78;i:8299;i:79;i:8300;i:80;i:8301;i:81;i:8302;i:82;i:8303;i:83;i:917505;}s:15:"prohibit_ranges";a:10:{i:0;a:2:{i:0;i:128;i:1;i:159;}i:1;a:2:{i:0;i:8288;i:1;i:8303;}i:2;a:2:{i:0;i:119155;i:1;i:119162;}i:3;a:2:{i:0;i:57344;i:1;i:63743;}i:4;a:2:{i:0;i:983040;i:1;i:1048573;}i:5;a:2:{i:0;i:1048576;i:1;i:1114109;}i:6;a:2:{i:0;i:64976;i:1;i:65007;}i:7;a:2:{i:0;i:55296;i:1;i:57343;}i:8;a:2:{i:0;i:12272;i:1;i:12283;}i:9;a:2:{i:0;i:917536;i:1;i:917631;}}s:11:"replacemaps";a:1401:{i:65;a:1:{i:0;i:97;}i:66;a:1:{i:0;i:98;}i:67;a:1:{i:0;i:99;}i:68;a:1:{i:0;i:100;}i:69;a:1:{i:0;i:101;}i:70;a:1:{i:0;i:102;}i:71;a:1:{i:0;i:103;}i:72;a:1:{i:0;i:104;}i:73;a:1:{i:0;i:105;}i:74;a:1:{i:0;i:106;}i:75;a:1:{i:0;i:107;}i:76;a:1:{i:0;i:108;}i:77;a:1:{i:0;i:109;}i:78;a:1:{i:0;i:110;}i:79;a:1:{i:0;i:111;}i:80;a:1:{i:0;i:112;}i:81;a:1:{i:0;i:113;}i:82;a:1:{i:0;i:114;}i:83;a:1:{i:0;i:115;}i:84;a:1:{i:0;i:116;}i:85;a:1:{i:0;i:117;}i:86;a:1:{i:0;i:118;}i:87;a:1:{i:0;i:119;}i:88;a:1:{i:0;i:120;}i:89;a:1:{i:0;i:121;}i:90;a:1:{i:0;i:122;}i:181;a:1:{i:0;i:956;}i:192;a:1:{i:0;i:224;}i:193;a:1:{i:0;i:225;}i:194;a:1:{i:0;i:226;}i:195;a:1:{i:0;i:227;}i:196;a:1:{i:0;i:228;}i:197;a:1:{i:0;i:229;}i:198;a:1:{i:0;i:230;}i:199;a:1:{i:0;i:231;}i:200;a:1:{i:0;i:232;}i:201;a:1:{i:0;i:233;}i:202;a:1:{i:0;i:234;}i:203;a:1:{i:0;i:235;}i:204;a:1:{i:0;i:236;}i:205;a:1:{i:0;i:237;}i:206;a:1:{i:0;i:238;}i:207;a:1:{i:0;i:239;}i:208;a:1:{i:0;i:240;}i:209;a:1:{i:0;i:241;}i:210;a:1:{i:0;i:242;}i:211;a:1:{i:0;i:243;}i:212;a:1:{i:0;i:244;}i:213;a:1:{i:0;i:245;}i:214;a:1:{i:0;i:246;}i:216;a:1:{i:0;i:248;}i:217;a:1:{i:0;i:249;}i:218;a:1:{i:0;i:250;}i:219;a:1:{i:0;i:251;}i:220;a:1:{i:0;i:252;}i:221;a:1:{i:0;i:253;}i:222;a:1:{i:0;i:254;}i:223;a:2:{i:0;i:115;i:1;i:115;}i:256;a:1:{i:0;i:257;}i:258;a:1:{i:0;i:259;}i:260;a:1:{i:0;i:261;}i:262;a:1:{i:0;i:263;}i:264;a:1:{i:0;i:265;}i:266;a:1:{i:0;i:267;}i:268;a:1:{i:0;i:269;}i:270;a:1:{i:0;i:271;}i:272;a:1:{i:0;i:273;}i:274;a:1:{i:0;i:275;}i:276;a:1:{i:0;i:277;}i:278;a:1:{i:0;i:279;}i:280;a:1:{i:0;i:281;}i:282;a:1:{i:0;i:283;}i:284;a:1:{i:0;i:285;}i:286;a:1:{i:0;i:287;}i:288;a:1:{i:0;i:289;}i:290;a:1:{i:0;i:291;}i:292;a:1:{i:0;i:293;}i:294;a:1:{i:0;i:295;}i:296;a:1:{i:0;i:297;}i:298;a:1:{i:0;i:299;}i:300;a:1:{i:0;i:301;}i:302;a:1:{i:0;i:303;}i:304;a:2:{i:0;i:105;i:1;i:775;}i:306;a:1:{i:0;i:307;}i:308;a:1:{i:0;i:309;}i:310;a:1:{i:0;i:311;}i:313;a:1:{i:0;i:314;}i:315;a:1:{i:0;i:316;}i:317;a:1:{i:0;i:318;}i:319;a:1:{i:0;i:320;}i:321;a:1:{i:0;i:322;}i:323;a:1:{i:0;i:324;}i:325;a:1:{i:0;i:326;}i:327;a:1:{i:0;i:328;}i:329;a:2:{i:0;i:700;i:1;i:110;}i:330;a:1:{i:0;i:331;}i:332;a:1:{i:0;i:333;}i:334;a:1:{i:0;i:335;}i:336;a:1:{i:0;i:337;}i:338;a:1:{i:0;i:339;}i:340;a:1:{i:0;i:341;}i:342;a:1:{i:0;i:343;}i:344;a:1:{i:0;i:345;}i:346;a:1:{i:0;i:347;}i:348;a:1:{i:0;i:349;}i:350;a:1:{i:0;i:351;}i:352;a:1:{i:0;i:353;}i:354;a:1:{i:0;i:355;}i:356;a:1:{i:0;i:357;}i:358;a:1:{i:0;i:359;}i:360;a:1:{i:0;i:361;}i:362;a:1:{i:0;i:363;}i:364;a:1:{i:0;i:365;}i:366;a:1:{i:0;i:367;}i:368;a:1:{i:0;i:369;}i:370;a:1:{i:0;i:371;}i:372;a:1:{i:0;i:373;}i:374;a:1:{i:0;i:375;}i:376;a:1:{i:0;i:255;}i:377;a:1:{i:0;i:378;}i:379;a:1:{i:0;i:380;}i:381;a:1:{i:0;i:382;}i:383;a:1:{i:0;i:115;}i:385;a:1:{i:0;i:595;}i:386;a:1:{i:0;i:387;}i:388;a:1:{i:0;i:389;}i:390;a:1:{i:0;i:596;}i:391;a:1:{i:0;i:392;}i:393;a:1:{i:0;i:598;}i:394;a:1:{i:0;i:599;}i:395;a:1:{i:0;i:396;}i:398;a:1:{i:0;i:477;}i:399;a:1:{i:0;i:601;}i:400;a:1:{i:0;i:603;}i:401;a:1:{i:0;i:402;}i:403;a:1:{i:0;i:608;}i:404;a:1:{i:0;i:611;}i:406;a:1:{i:0;i:617;}i:407;a:1:{i:0;i:616;}i:408;a:1:{i:0;i:409;}i:412;a:1:{i:0;i:623;}i:413;a:1:{i:0;i:626;}i:415;a:1:{i:0;i:629;}i:416;a:1:{i:0;i:417;}i:418;a:1:{i:0;i:419;}i:420;a:1:{i:0;i:421;}i:422;a:1:{i:0;i:640;}i:423;a:1:{i:0;i:424;}i:425;a:1:{i:0;i:643;}i:428;a:1:{i:0;i:429;}i:430;a:1:{i:0;i:648;}i:431;a:1:{i:0;i:432;}i:433;a:1:{i:0;i:650;}i:434;a:1:{i:0;i:651;}i:435;a:1:{i:0;i:436;}i:437;a:1:{i:0;i:438;}i:439;a:1:{i:0;i:658;}i:440;a:1:{i:0;i:441;}i:444;a:1:{i:0;i:445;}i:452;a:1:{i:0;i:454;}i:453;a:1:{i:0;i:454;}i:455;a:1:{i:0;i:457;}i:456;a:1:{i:0;i:457;}i:458;a:1:{i:0;i:460;}i:459;a:1:{i:0;i:460;}i:461;a:1:{i:0;i:462;}i:463;a:1:{i:0;i:464;}i:465;a:1:{i:0;i:466;}i:467;a:1:{i:0;i:468;}i:469;a:1:{i:0;i:470;}i:471;a:1:{i:0;i:472;}i:473;a:1:{i:0;i:474;}i:475;a:1:{i:0;i:476;}i:478;a:1:{i:0;i:479;}i:480;a:1:{i:0;i:481;}i:482;a:1:{i:0;i:483;}i:484;a:1:{i:0;i:485;}i:486;a:1:{i:0;i:487;}i:488;a:1:{i:0;i:489;}i:490;a:1:{i:0;i:491;}i:492;a:1:{i:0;i:493;}i:494;a:1:{i:0;i:495;}i:496;a:2:{i:0;i:106;i:1;i:780;}i:497;a:1:{i:0;i:499;}i:498;a:1:{i:0;i:499;}i:500;a:1:{i:0;i:501;}i:502;a:1:{i:0;i:405;}i:503;a:1:{i:0;i:447;}i:504;a:1:{i:0;i:505;}i:506;a:1:{i:0;i:507;}i:508;a:1:{i:0;i:509;}i:510;a:1:{i:0;i:511;}i:512;a:1:{i:0;i:513;}i:514;a:1:{i:0;i:515;}i:516;a:1:{i:0;i:517;}i:518;a:1:{i:0;i:519;}i:520;a:1:{i:0;i:521;}i:522;a:1:{i:0;i:523;}i:524;a:1:{i:0;i:525;}i:526;a:1:{i:0;i:527;}i:528;a:1:{i:0;i:529;}i:530;a:1:{i:0;i:531;}i:532;a:1:{i:0;i:533;}i:534;a:1:{i:0;i:535;}i:536;a:1:{i:0;i:537;}i:538;a:1:{i:0;i:539;}i:540;a:1:{i:0;i:541;}i:542;a:1:{i:0;i:543;}i:544;a:1:{i:0;i:414;}i:546;a:1:{i:0;i:547;}i:548;a:1:{i:0;i:549;}i:550;a:1:{i:0;i:551;}i:552;a:1:{i:0;i:553;}i:554;a:1:{i:0;i:555;}i:556;a:1:{i:0;i:557;}i:558;a:1:{i:0;i:559;}i:560;a:1:{i:0;i:561;}i:562;a:1:{i:0;i:563;}i:837;a:1:{i:0;i:953;}i:890;a:2:{i:0;i:32;i:1;i:953;}i:902;a:1:{i:0;i:940;}i:904;a:1:{i:0;i:941;}i:905;a:1:{i:0;i:942;}i:906;a:1:{i:0;i:943;}i:908;a:1:{i:0;i:972;}i:910;a:1:{i:0;i:973;}i:911;a:1:{i:0;i:974;}i:912;a:3:{i:0;i:953;i:1;i:776;i:2;i:769;}i:913;a:1:{i:0;i:945;}i:914;a:1:{i:0;i:946;}i:915;a:1:{i:0;i:947;}i:916;a:1:{i:0;i:948;}i:917;a:1:{i:0;i:949;}i:918;a:1:{i:0;i:950;}i:919;a:1:{i:0;i:951;}i:920;a:1:{i:0;i:952;}i:921;a:1:{i:0;i:953;}i:922;a:1:{i:0;i:954;}i:923;a:1:{i:0;i:955;}i:924;a:1:{i:0;i:956;}i:925;a:1:{i:0;i:957;}i:926;a:1:{i:0;i:958;}i:927;a:1:{i:0;i:959;}i:928;a:1:{i:0;i:960;}i:929;a:1:{i:0;i:961;}i:931;a:1:{i:0;i:963;}i:932;a:1:{i:0;i:964;}i:933;a:1:{i:0;i:965;}i:934;a:1:{i:0;i:966;}i:935;a:1:{i:0;i:967;}i:936;a:1:{i:0;i:968;}i:937;a:1:{i:0;i:969;}i:938;a:1:{i:0;i:970;}i:939;a:1:{i:0;i:971;}i:944;a:3:{i:0;i:965;i:1;i:776;i:2;i:769;}i:962;a:1:{i:0;i:963;}i:976;a:1:{i:0;i:946;}i:977;a:1:{i:0;i:952;}i:978;a:1:{i:0;i:965;}i:979;a:1:{i:0;i:973;}i:980;a:1:{i:0;i:971;}i:981;a:1:{i:0;i:966;}i:982;a:1:{i:0;i:960;}i:984;a:1:{i:0;i:985;}i:986;a:1:{i:0;i:987;}i:988;a:1:{i:0;i:989;}i:990;a:1:{i:0;i:991;}i:992;a:1:{i:0;i:993;}i:994;a:1:{i:0;i:995;}i:996;a:1:{i:0;i:997;}i:998;a:1:{i:0;i:999;}i:1000;a:1:{i:0;i:1001;}i:1002;a:1:{i:0;i:1003;}i:1004;a:1:{i:0;i:1005;}i:1006;a:1:{i:0;i:1007;}i:1008;a:1:{i:0;i:954;}i:1009;a:1:{i:0;i:961;}i:1010;a:1:{i:0;i:963;}i:1012;a:1:{i:0;i:952;}i:1013;a:1:{i:0;i:949;}i:1024;a:1:{i:0;i:1104;}i:1025;a:1:{i:0;i:1105;}i:1026;a:1:{i:0;i:1106;}i:1027;a:1:{i:0;i:1107;}i:1028;a:1:{i:0;i:1108;}i:1029;a:1:{i:0;i:1109;}i:1030;a:1:{i:0;i:1110;}i:1031;a:1:{i:0;i:1111;}i:1032;a:1:{i:0;i:1112;}i:1033;a:1:{i:0;i:1113;}i:1034;a:1:{i:0;i:1114;}i:1035;a:1:{i:0;i:1115;}i:1036;a:1:{i:0;i:1116;}i:1037;a:1:{i:0;i:1117;}i:1038;a:1:{i:0;i:1118;}i:1039;a:1:{i:0;i:1119;}i:1040;a:1:{i:0;i:1072;}i:1041;a:1:{i:0;i:1073;}i:1042;a:1:{i:0;i:1074;}i:1043;a:1:{i:0;i:1075;}i:1044;a:1:{i:0;i:1076;}i:1045;a:1:{i:0;i:1077;}i:1046;a:1:{i:0;i:1078;}i:1047;a:1:{i:0;i:1079;}i:1048;a:1:{i:0;i:1080;}i:1049;a:1:{i:0;i:1081;}i:1050;a:1:{i:0;i:1082;}i:1051;a:1:{i:0;i:1083;}i:1052;a:1:{i:0;i:1084;}i:1053;a:1:{i:0;i:1085;}i:1054;a:1:{i:0;i:1086;}i:1055;a:1:{i:0;i:1087;}i:1056;a:1:{i:0;i:1088;}i:1057;a:1:{i:0;i:1089;}i:1058;a:1:{i:0;i:1090;}i:1059;a:1:{i:0;i:1091;}i:1060;a:1:{i:0;i:1092;}i:1061;a:1:{i:0;i:1093;}i:1062;a:1:{i:0;i:1094;}i:1063;a:1:{i:0;i:1095;}i:1064;a:1:{i:0;i:1096;}i:1065;a:1:{i:0;i:1097;}i:1066;a:1:{i:0;i:1098;}i:1067;a:1:{i:0;i:1099;}i:1068;a:1:{i:0;i:1100;}i:1069;a:1:{i:0;i:1101;}i:1070;a:1:{i:0;i:1102;}i:1071;a:1:{i:0;i:1103;}i:1120;a:1:{i:0;i:1121;}i:1122;a:1:{i:0;i:1123;}i:1124;a:1:{i:0;i:1125;}i:1126;a:1:{i:0;i:1127;}i:1128;a:1:{i:0;i:1129;}i:1130;a:1:{i:0;i:1131;}i:1132;a:1:{i:0;i:1133;}i:1134;a:1:{i:0;i:1135;}i:1136;a:1:{i:0;i:1137;}i:1138;a:1:{i:0;i:1139;}i:1140;a:1:{i:0;i:1141;}i:1142;a:1:{i:0;i:1143;}i:1144;a:1:{i:0;i:1145;}i:1146;a:1:{i:0;i:1147;}i:1148;a:1:{i:0;i:1149;}i:1150;a:1:{i:0;i:1151;}i:1152;a:1:{i:0;i:1153;}i:1162;a:1:{i:0;i:1163;}i:1164;a:1:{i:0;i:1165;}i:1166;a:1:{i:0;i:1167;}i:1168;a:1:{i:0;i:1169;}i:1170;a:1:{i:0;i:1171;}i:1172;a:1:{i:0;i:1173;}i:1174;a:1:{i:0;i:1175;}i:1176;a:1:{i:0;i:1177;}i:1178;a:1:{i:0;i:1179;}i:1180;a:1:{i:0;i:1181;}i:1182;a:1:{i:0;i:1183;}i:1184;a:1:{i:0;i:1185;}i:1186;a:1:{i:0;i:1187;}i:1188;a:1:{i:0;i:1189;}i:1190;a:1:{i:0;i:1191;}i:1192;a:1:{i:0;i:1193;}i:1194;a:1:{i:0;i:1195;}i:1196;a:1:{i:0;i:1197;}i:1198;a:1:{i:0;i:1199;}i:1200;a:1:{i:0;i:1201;}i:1202;a:1:{i:0;i:1203;}i:1204;a:1:{i:0;i:1205;}i:1206;a:1:{i:0;i:1207;}i:1208;a:1:{i:0;i:1209;}i:1210;a:1:{i:0;i:1211;}i:1212;a:1:{i:0;i:1213;}i:1214;a:1:{i:0;i:1215;}i:1217;a:1:{i:0;i:1218;}i:1219;a:1:{i:0;i:1220;}i:1221;a:1:{i:0;i:1222;}i:1223;a:1:{i:0;i:1224;}i:1225;a:1:{i:0;i:1226;}i:1227;a:1:{i:0;i:1228;}i:1229;a:1:{i:0;i:1230;}i:1232;a:1:{i:0;i:1233;}i:1234;a:1:{i:0;i:1235;}i:1236;a:1:{i:0;i:1237;}i:1238;a:1:{i:0;i:1239;}i:1240;a:1:{i:0;i:1241;}i:1242;a:1:{i:0;i:1243;}i:1244;a:1:{i:0;i:1245;}i:1246;a:1:{i:0;i:1247;}i:1248;a:1:{i:0;i:1249;}i:1250;a:1:{i:0;i:1251;}i:1252;a:1:{i:0;i:1253;}i:1254;a:1:{i:0;i:1255;}i:1256;a:1:{i:0;i:1257;}i:1258;a:1:{i:0;i:1259;}i:1260;a:1:{i:0;i:1261;}i:1262;a:1:{i:0;i:1263;}i:1264;a:1:{i:0;i:1265;}i:1266;a:1:{i:0;i:1267;}i:1268;a:1:{i:0;i:1269;}i:1272;a:1:{i:0;i:1273;}i:1280;a:1:{i:0;i:1281;}i:1282;a:1:{i:0;i:1283;}i:1284;a:1:{i:0;i:1285;}i:1286;a:1:{i:0;i:1287;}i:1288;a:1:{i:0;i:1289;}i:1290;a:1:{i:0;i:1291;}i:1292;a:1:{i:0;i:1293;}i:1294;a:1:{i:0;i:1295;}i:1329;a:1:{i:0;i:1377;}i:1330;a:1:{i:0;i:1378;}i:1331;a:1:{i:0;i:1379;}i:1332;a:1:{i:0;i:1380;}i:1333;a:1:{i:0;i:1381;}i:1334;a:1:{i:0;i:1382;}i:1335;a:1:{i:0;i:1383;}i:1336;a:1:{i:0;i:1384;}i:1337;a:1:{i:0;i:1385;}i:1338;a:1:{i:0;i:1386;}i:1339;a:1:{i:0;i:1387;}i:1340;a:1:{i:0;i:1388;}i:1341;a:1:{i:0;i:1389;}i:1342;a:1:{i:0;i:1390;}i:1343;a:1:{i:0;i:1391;}i:1344;a:1:{i:0;i:1392;}i:1345;a:1:{i:0;i:1393;}i:1346;a:1:{i:0;i:1394;}i:1347;a:1:{i:0;i:1395;}i:1348;a:1:{i:0;i:1396;}i:1349;a:1:{i:0;i:1397;}i:1350;a:1:{i:0;i:1398;}i:1351;a:1:{i:0;i:1399;}i:1352;a:1:{i:0;i:1400;}i:1353;a:1:{i:0;i:1401;}i:1354;a:1:{i:0;i:1402;}i:1355;a:1:{i:0;i:1403;}i:1356;a:1:{i:0;i:1404;}i:1357;a:1:{i:0;i:1405;}i:1358;a:1:{i:0;i:1406;}i:1359;a:1:{i:0;i:1407;}i:1360;a:1:{i:0;i:1408;}i:1361;a:1:{i:0;i:1409;}i:1362;a:1:{i:0;i:1410;}i:1363;a:1:{i:0;i:1411;}i:1364;a:1:{i:0;i:1412;}i:1365;a:1:{i:0;i:1413;}i:1366;a:1:{i:0;i:1414;}i:1415;a:2:{i:0;i:1381;i:1;i:1410;}i:7680;a:1:{i:0;i:7681;}i:7682;a:1:{i:0;i:7683;}i:7684;a:1:{i:0;i:7685;}i:7686;a:1:{i:0;i:7687;}i:7688;a:1:{i:0;i:7689;}i:7690;a:1:{i:0;i:7691;}i:7692;a:1:{i:0;i:7693;}i:7694;a:1:{i:0;i:7695;}i:7696;a:1:{i:0;i:7697;}i:7698;a:1:{i:0;i:7699;}i:7700;a:1:{i:0;i:7701;}i:7702;a:1:{i:0;i:7703;}i:7704;a:1:{i:0;i:7705;}i:7706;a:1:{i:0;i:7707;}i:7708;a:1:{i:0;i:7709;}i:7710;a:1:{i:0;i:7711;}i:7712;a:1:{i:0;i:7713;}i:7714;a:1:{i:0;i:7715;}i:7716;a:1:{i:0;i:7717;}i:7718;a:1:{i:0;i:7719;}i:7720;a:1:{i:0;i:7721;}i:7722;a:1:{i:0;i:7723;}i:7724;a:1:{i:0;i:7725;}i:7726;a:1:{i:0;i:7727;}i:7728;a:1:{i:0;i:7729;}i:7730;a:1:{i:0;i:7731;}i:7732;a:1:{i:0;i:7733;}i:7734;a:1:{i:0;i:7735;}i:7736;a:1:{i:0;i:7737;}i:7738;a:1:{i:0;i:7739;}i:7740;a:1:{i:0;i:7741;}i:7742;a:1:{i:0;i:7743;}i:7744;a:1:{i:0;i:7745;}i:7746;a:1:{i:0;i:7747;}i:7748;a:1:{i:0;i:7749;}i:7750;a:1:{i:0;i:7751;}i:7752;a:1:{i:0;i:7753;}i:7754;a:1:{i:0;i:7755;}i:7756;a:1:{i:0;i:7757;}i:7758;a:1:{i:0;i:7759;}i:7760;a:1:{i:0;i:7761;}i:7762;a:1:{i:0;i:7763;}i:7764;a:1:{i:0;i:7765;}i:7766;a:1:{i:0;i:7767;}i:7768;a:1:{i:0;i:7769;}i:7770;a:1:{i:0;i:7771;}i:7772;a:1:{i:0;i:7773;}i:7774;a:1:{i:0;i:7775;}i:7776;a:1:{i:0;i:7777;}i:7778;a:1:{i:0;i:7779;}i:7780;a:1:{i:0;i:7781;}i:7782;a:1:{i:0;i:7783;}i:7784;a:1:{i:0;i:7785;}i:7786;a:1:{i:0;i:7787;}i:7788;a:1:{i:0;i:7789;}i:7790;a:1:{i:0;i:7791;}i:7792;a:1:{i:0;i:7793;}i:7794;a:1:{i:0;i:7795;}i:7796;a:1:{i:0;i:7797;}i:7798;a:1:{i:0;i:7799;}i:7800;a:1:{i:0;i:7801;}i:7802;a:1:{i:0;i:7803;}i:7804;a:1:{i:0;i:7805;}i:7806;a:1:{i:0;i:7807;}i:7808;a:1:{i:0;i:7809;}i:7810;a:1:{i:0;i:7811;}i:7812;a:1:{i:0;i:7813;}i:7814;a:1:{i:0;i:7815;}i:7816;a:1:{i:0;i:7817;}i:7818;a:1:{i:0;i:7819;}i:7820;a:1:{i:0;i:7821;}i:7822;a:1:{i:0;i:7823;}i:7824;a:1:{i:0;i:7825;}i:7826;a:1:{i:0;i:7827;}i:7828;a:1:{i:0;i:7829;}i:7830;a:2:{i:0;i:104;i:1;i:817;}i:7831;a:2:{i:0;i:116;i:1;i:776;}i:7832;a:2:{i:0;i:119;i:1;i:778;}i:7833;a:2:{i:0;i:121;i:1;i:778;}i:7834;a:2:{i:0;i:97;i:1;i:702;}i:7835;a:1:{i:0;i:7777;}i:7840;a:1:{i:0;i:7841;}i:7842;a:1:{i:0;i:7843;}i:7844;a:1:{i:0;i:7845;}i:7846;a:1:{i:0;i:7847;}i:7848;a:1:{i:0;i:7849;}i:7850;a:1:{i:0;i:7851;}i:7852;a:1:{i:0;i:7853;}i:7854;a:1:{i:0;i:7855;}i:7856;a:1:{i:0;i:7857;}i:7858;a:1:{i:0;i:7859;}i:7860;a:1:{i:0;i:7861;}i:7862;a:1:{i:0;i:7863;}i:7864;a:1:{i:0;i:7865;}i:7866;a:1:{i:0;i:7867;}i:7868;a:1:{i:0;i:7869;}i:7870;a:1:{i:0;i:7871;}i:7872;a:1:{i:0;i:7873;}i:7874;a:1:{i:0;i:7875;}i:7876;a:1:{i:0;i:7877;}i:7878;a:1:{i:0;i:7879;}i:7880;a:1:{i:0;i:7881;}i:7882;a:1:{i:0;i:7883;}i:7884;a:1:{i:0;i:7885;}i:7886;a:1:{i:0;i:7887;}i:7888;a:1:{i:0;i:7889;}i:7890;a:1:{i:0;i:7891;}i:7892;a:1:{i:0;i:7893;}i:7894;a:1:{i:0;i:7895;}i:7896;a:1:{i:0;i:7897;}i:7898;a:1:{i:0;i:7899;}i:7900;a:1:{i:0;i:7901;}i:7902;a:1:{i:0;i:7903;}i:7904;a:1:{i:0;i:7905;}i:7906;a:1:{i:0;i:7907;}i:7908;a:1:{i:0;i:7909;}i:7910;a:1:{i:0;i:7911;}i:7912;a:1:{i:0;i:7913;}i:7914;a:1:{i:0;i:7915;}i:7916;a:1:{i:0;i:7917;}i:7918;a:1:{i:0;i:7919;}i:7920;a:1:{i:0;i:7921;}i:7922;a:1:{i:0;i:7923;}i:7924;a:1:{i:0;i:7925;}i:7926;a:1:{i:0;i:7927;}i:7928;a:1:{i:0;i:7929;}i:7944;a:1:{i:0;i:7936;}i:7945;a:1:{i:0;i:7937;}i:7946;a:1:{i:0;i:7938;}i:7947;a:1:{i:0;i:7939;}i:7948;a:1:{i:0;i:7940;}i:7949;a:1:{i:0;i:7941;}i:7950;a:1:{i:0;i:7942;}i:7951;a:1:{i:0;i:7943;}i:7960;a:1:{i:0;i:7952;}i:7961;a:1:{i:0;i:7953;}i:7962;a:1:{i:0;i:7954;}i:7963;a:1:{i:0;i:7955;}i:7964;a:1:{i:0;i:7956;}i:7965;a:1:{i:0;i:7957;}i:7976;a:1:{i:0;i:7968;}i:7977;a:1:{i:0;i:7969;}i:7978;a:1:{i:0;i:7970;}i:7979;a:1:{i:0;i:7971;}i:7980;a:1:{i:0;i:7972;}i:7981;a:1:{i:0;i:7973;}i:7982;a:1:{i:0;i:7974;}i:7983;a:1:{i:0;i:7975;}i:7992;a:1:{i:0;i:7984;}i:7993;a:1:{i:0;i:7985;}i:7994;a:1:{i:0;i:7986;}i:7995;a:1:{i:0;i:7987;}i:7996;a:1:{i:0;i:7988;}i:7997;a:1:{i:0;i:7989;}i:7998;a:1:{i:0;i:7990;}i:7999;a:1:{i:0;i:7991;}i:8008;a:1:{i:0;i:8000;}i:8009;a:1:{i:0;i:8001;}i:8010;a:1:{i:0;i:8002;}i:8011;a:1:{i:0;i:8003;}i:8012;a:1:{i:0;i:8004;}i:8013;a:1:{i:0;i:8005;}i:8016;a:2:{i:0;i:965;i:1;i:787;}i:8018;a:3:{i:0;i:965;i:1;i:787;i:2;i:768;}i:8020;a:3:{i:0;i:965;i:1;i:787;i:2;i:769;}i:8022;a:3:{i:0;i:965;i:1;i:787;i:2;i:834;}i:8025;a:1:{i:0;i:8017;}i:8027;a:1:{i:0;i:8019;}i:8029;a:1:{i:0;i:8021;}i:8031;a:1:{i:0;i:8023;}i:8040;a:1:{i:0;i:8032;}i:8041;a:1:{i:0;i:8033;}i:8042;a:1:{i:0;i:8034;}i:8043;a:1:{i:0;i:8035;}i:8044;a:1:{i:0;i:8036;}i:8045;a:1:{i:0;i:8037;}i:8046;a:1:{i:0;i:8038;}i:8047;a:1:{i:0;i:8039;}i:8064;a:2:{i:0;i:7936;i:1;i:953;}i:8065;a:2:{i:0;i:7937;i:1;i:953;}i:8066;a:2:{i:0;i:7938;i:1;i:953;}i:8067;a:2:{i:0;i:7939;i:1;i:953;}i:8068;a:2:{i:0;i:7940;i:1;i:953;}i:8069;a:2:{i:0;i:7941;i:1;i:953;}i:8070;a:2:{i:0;i:7942;i:1;i:953;}i:8071;a:2:{i:0;i:7943;i:1;i:953;}i:8072;a:2:{i:0;i:7936;i:1;i:953;}i:8073;a:2:{i:0;i:7937;i:1;i:953;}i:8074;a:2:{i:0;i:7938;i:1;i:953;}i:8075;a:2:{i:0;i:7939;i:1;i:953;}i:8076;a:2:{i:0;i:7940;i:1;i:953;}i:8077;a:2:{i:0;i:7941;i:1;i:953;}i:8078;a:2:{i:0;i:7942;i:1;i:953;}i:8079;a:2:{i:0;i:7943;i:1;i:953;}i:8080;a:2:{i:0;i:7968;i:1;i:953;}i:8081;a:2:{i:0;i:7969;i:1;i:953;}i:8082;a:2:{i:0;i:7970;i:1;i:953;}i:8083;a:2:{i:0;i:7971;i:1;i:953;}i:8084;a:2:{i:0;i:7972;i:1;i:953;}i:8085;a:2:{i:0;i:7973;i:1;i:953;}i:8086;a:2:{i:0;i:7974;i:1;i:953;}i:8087;a:2:{i:0;i:7975;i:1;i:953;}i:8088;a:2:{i:0;i:7968;i:1;i:953;}i:8089;a:2:{i:0;i:7969;i:1;i:953;}i:8090;a:2:{i:0;i:7970;i:1;i:953;}i:8091;a:2:{i:0;i:7971;i:1;i:953;}i:8092;a:2:{i:0;i:7972;i:1;i:953;}i:8093;a:2:{i:0;i:7973;i:1;i:953;}i:8094;a:2:{i:0;i:7974;i:1;i:953;}i:8095;a:2:{i:0;i:7975;i:1;i:953;}i:8096;a:2:{i:0;i:8032;i:1;i:953;}i:8097;a:2:{i:0;i:8033;i:1;i:953;}i:8098;a:2:{i:0;i:8034;i:1;i:953;}i:8099;a:2:{i:0;i:8035;i:1;i:953;}i:8100;a:2:{i:0;i:8036;i:1;i:953;}i:8101;a:2:{i:0;i:8037;i:1;i:953;}i:8102;a:2:{i:0;i:8038;i:1;i:953;}i:8103;a:2:{i:0;i:8039;i:1;i:953;}i:8104;a:2:{i:0;i:8032;i:1;i:953;}i:8105;a:2:{i:0;i:8033;i:1;i:953;}i:8106;a:2:{i:0;i:8034;i:1;i:953;}i:8107;a:2:{i:0;i:8035;i:1;i:953;}i:8108;a:2:{i:0;i:8036;i:1;i:953;}i:8109;a:2:{i:0;i:8037;i:1;i:953;}i:8110;a:2:{i:0;i:8038;i:1;i:953;}i:8111;a:2:{i:0;i:8039;i:1;i:953;}i:8114;a:2:{i:0;i:8048;i:1;i:953;}i:8115;a:2:{i:0;i:945;i:1;i:953;}i:8116;a:2:{i:0;i:940;i:1;i:953;}i:8118;a:2:{i:0;i:945;i:1;i:834;}i:8119;a:3:{i:0;i:945;i:1;i:834;i:2;i:953;}i:8120;a:1:{i:0;i:8112;}i:8121;a:1:{i:0;i:8113;}i:8122;a:1:{i:0;i:8048;}i:8123;a:1:{i:0;i:8049;}i:8124;a:2:{i:0;i:945;i:1;i:953;}i:8126;a:1:{i:0;i:953;}i:8130;a:2:{i:0;i:8052;i:1;i:953;}i:8131;a:2:{i:0;i:951;i:1;i:953;}i:8132;a:2:{i:0;i:942;i:1;i:953;}i:8134;a:2:{i:0;i:951;i:1;i:834;}i:8135;a:3:{i:0;i:951;i:1;i:834;i:2;i:953;}i:8136;a:1:{i:0;i:8050;}i:8137;a:1:{i:0;i:8051;}i:8138;a:1:{i:0;i:8052;}i:8139;a:1:{i:0;i:8053;}i:8140;a:2:{i:0;i:951;i:1;i:953;}i:8146;a:3:{i:0;i:953;i:1;i:776;i:2;i:768;}i:8147;a:3:{i:0;i:953;i:1;i:776;i:2;i:769;}i:8150;a:2:{i:0;i:953;i:1;i:834;}i:8151;a:3:{i:0;i:953;i:1;i:776;i:2;i:834;}i:8152;a:1:{i:0;i:8144;}i:8153;a:1:{i:0;i:8145;}i:8154;a:1:{i:0;i:8054;}i:8155;a:1:{i:0;i:8055;}i:8162;a:3:{i:0;i:965;i:1;i:776;i:2;i:768;}i:8163;a:3:{i:0;i:965;i:1;i:776;i:2;i:769;}i:8164;a:2:{i:0;i:961;i:1;i:787;}i:8166;a:2:{i:0;i:965;i:1;i:834;}i:8167;a:3:{i:0;i:965;i:1;i:776;i:2;i:834;}i:8168;a:1:{i:0;i:8160;}i:8169;a:1:{i:0;i:8161;}i:8170;a:1:{i:0;i:8058;}i:8171;a:1:{i:0;i:8059;}i:8172;a:1:{i:0;i:8165;}i:8178;a:2:{i:0;i:8060;i:1;i:953;}i:8179;a:2:{i:0;i:969;i:1;i:953;}i:8180;a:2:{i:0;i:974;i:1;i:953;}i:8182;a:2:{i:0;i:969;i:1;i:834;}i:8183;a:3:{i:0;i:969;i:1;i:834;i:2;i:953;}i:8184;a:1:{i:0;i:8056;}i:8185;a:1:{i:0;i:8057;}i:8186;a:1:{i:0;i:8060;}i:8187;a:1:{i:0;i:8061;}i:8188;a:2:{i:0;i:969;i:1;i:953;}i:8360;a:2:{i:0;i:114;i:1;i:115;}i:8450;a:1:{i:0;i:99;}i:8451;a:2:{i:0;i:176;i:1;i:99;}i:8455;a:1:{i:0;i:603;}i:8457;a:2:{i:0;i:176;i:1;i:102;}i:8459;a:1:{i:0;i:104;}i:8460;a:1:{i:0;i:104;}i:8461;a:1:{i:0;i:104;}i:8464;a:1:{i:0;i:105;}i:8465;a:1:{i:0;i:105;}i:8466;a:1:{i:0;i:108;}i:8469;a:1:{i:0;i:110;}i:8470;a:2:{i:0;i:110;i:1;i:111;}i:8473;a:1:{i:0;i:112;}i:8474;a:1:{i:0;i:113;}i:8475;a:1:{i:0;i:114;}i:8476;a:1:{i:0;i:114;}i:8477;a:1:{i:0;i:114;}i:8480;a:2:{i:0;i:115;i:1;i:109;}i:8481;a:3:{i:0;i:116;i:1;i:101;i:2;i:108;}i:8482;a:2:{i:0;i:116;i:1;i:109;}i:8484;a:1:{i:0;i:122;}i:8486;a:1:{i:0;i:969;}i:8488;a:1:{i:0;i:122;}i:8490;a:1:{i:0;i:107;}i:8491;a:1:{i:0;i:229;}i:8492;a:1:{i:0;i:98;}i:8493;a:1:{i:0;i:99;}i:8496;a:1:{i:0;i:101;}i:8497;a:1:{i:0;i:102;}i:8499;a:1:{i:0;i:109;}i:8510;a:1:{i:0;i:947;}i:8511;a:1:{i:0;i:960;}i:8517;a:1:{i:0;i:100;}i:8544;a:1:{i:0;i:8560;}i:8545;a:1:{i:0;i:8561;}i:8546;a:1:{i:0;i:8562;}i:8547;a:1:{i:0;i:8563;}i:8548;a:1:{i:0;i:8564;}i:8549;a:1:{i:0;i:8565;}i:8550;a:1:{i:0;i:8566;}i:8551;a:1:{i:0;i:8567;}i:8552;a:1:{i:0;i:8568;}i:8553;a:1:{i:0;i:8569;}i:8554;a:1:{i:0;i:8570;}i:8555;a:1:{i:0;i:8571;}i:8556;a:1:{i:0;i:8572;}i:8557;a:1:{i:0;i:8573;}i:8558;a:1:{i:0;i:8574;}i:8559;a:1:{i:0;i:8575;}i:9398;a:1:{i:0;i:9424;}i:9399;a:1:{i:0;i:9425;}i:9400;a:1:{i:0;i:9426;}i:9401;a:1:{i:0;i:9427;}i:9402;a:1:{i:0;i:9428;}i:9403;a:1:{i:0;i:9429;}i:9404;a:1:{i:0;i:9430;}i:9405;a:1:{i:0;i:9431;}i:9406;a:1:{i:0;i:9432;}i:9407;a:1:{i:0;i:9433;}i:9408;a:1:{i:0;i:9434;}i:9409;a:1:{i:0;i:9435;}i:9410;a:1:{i:0;i:9436;}i:9411;a:1:{i:0;i:9437;}i:9412;a:1:{i:0;i:9438;}i:9413;a:1:{i:0;i:9439;}i:9414;a:1:{i:0;i:9440;}i:9415;a:1:{i:0;i:9441;}i:9416;a:1:{i:0;i:9442;}i:9417;a:1:{i:0;i:9443;}i:9418;a:1:{i:0;i:9444;}i:9419;a:1:{i:0;i:9445;}i:9420;a:1:{i:0;i:9446;}i:9421;a:1:{i:0;i:9447;}i:9422;a:1:{i:0;i:9448;}i:9423;a:1:{i:0;i:9449;}i:13169;a:3:{i:0;i:104;i:1;i:112;i:2;i:97;}i:13171;a:2:{i:0;i:97;i:1;i:117;}i:13173;a:2:{i:0;i:111;i:1;i:118;}i:13184;a:2:{i:0;i:112;i:1;i:97;}i:13185;a:2:{i:0;i:110;i:1;i:97;}i:13186;a:2:{i:0;i:956;i:1;i:97;}i:13187;a:2:{i:0;i:109;i:1;i:97;}i:13188;a:2:{i:0;i:107;i:1;i:97;}i:13189;a:2:{i:0;i:107;i:1;i:98;}i:13190;a:2:{i:0;i:109;i:1;i:98;}i:13191;a:2:{i:0;i:103;i:1;i:98;}i:13194;a:2:{i:0;i:112;i:1;i:102;}i:13195;a:2:{i:0;i:110;i:1;i:102;}i:13196;a:2:{i:0;i:956;i:1;i:102;}i:13200;a:2:{i:0;i:104;i:1;i:122;}i:13201;a:3:{i:0;i:107;i:1;i:104;i:2;i:122;}i:13202;a:3:{i:0;i:109;i:1;i:104;i:2;i:122;}i:13203;a:3:{i:0;i:103;i:1;i:104;i:2;i:122;}i:13204;a:3:{i:0;i:116;i:1;i:104;i:2;i:122;}i:13225;a:2:{i:0;i:112;i:1;i:97;}i:13226;a:3:{i:0;i:107;i:1;i:112;i:2;i:97;}i:13227;a:3:{i:0;i:109;i:1;i:112;i:2;i:97;}i:13228;a:3:{i:0;i:103;i:1;i:112;i:2;i:97;}i:13236;a:2:{i:0;i:112;i:1;i:118;}i:13237;a:2:{i:0;i:110;i:1;i:118;}i:13238;a:2:{i:0;i:956;i:1;i:118;}i:13239;a:2:{i:0;i:109;i:1;i:118;}i:13240;a:2:{i:0;i:107;i:1;i:118;}i:13241;a:2:{i:0;i:109;i:1;i:118;}i:13242;a:2:{i:0;i:112;i:1;i:119;}i:13243;a:2:{i:0;i:110;i:1;i:119;}i:13244;a:2:{i:0;i:956;i:1;i:119;}i:13245;a:2:{i:0;i:109;i:1;i:119;}i:13246;a:2:{i:0;i:107;i:1;i:119;}i:13247;a:2:{i:0;i:109;i:1;i:119;}i:13248;a:2:{i:0;i:107;i:1;i:969;}i:13249;a:2:{i:0;i:109;i:1;i:969;}i:13251;a:2:{i:0;i:98;i:1;i:113;}i:13254;a:4:{i:0;i:99;i:1;i:8725;i:2;i:107;i:3;i:103;}i:13255;a:3:{i:0;i:99;i:1;i:111;i:2;i:46;}i:13256;a:2:{i:0;i:100;i:1;i:98;}i:13257;a:2:{i:0;i:103;i:1;i:121;}i:13259;a:2:{i:0;i:104;i:1;i:112;}i:13261;a:2:{i:0;i:107;i:1;i:107;}i:13262;a:2:{i:0;i:107;i:1;i:109;}i:13271;a:2:{i:0;i:112;i:1;i:104;}i:13273;a:3:{i:0;i:112;i:1;i:112;i:2;i:109;}i:13274;a:2:{i:0;i:112;i:1;i:114;}i:13276;a:2:{i:0;i:115;i:1;i:118;}i:13277;a:2:{i:0;i:119;i:1;i:98;}i:64256;a:2:{i:0;i:102;i:1;i:102;}i:64257;a:2:{i:0;i:102;i:1;i:105;}i:64258;a:2:{i:0;i:102;i:1;i:108;}i:64259;a:3:{i:0;i:102;i:1;i:102;i:2;i:105;}i:64260;a:3:{i:0;i:102;i:1;i:102;i:2;i:108;}i:64261;a:2:{i:0;i:115;i:1;i:116;}i:64262;a:2:{i:0;i:115;i:1;i:116;}i:64275;a:2:{i:0;i:1396;i:1;i:1398;}i:64276;a:2:{i:0;i:1396;i:1;i:1381;}i:64277;a:2:{i:0;i:1396;i:1;i:1387;}i:64278;a:2:{i:0;i:1406;i:1;i:1398;}i:64279;a:2:{i:0;i:1396;i:1;i:1389;}i:65313;a:1:{i:0;i:65345;}i:65314;a:1:{i:0;i:65346;}i:65315;a:1:{i:0;i:65347;}i:65316;a:1:{i:0;i:65348;}i:65317;a:1:{i:0;i:65349;}i:65318;a:1:{i:0;i:65350;}i:65319;a:1:{i:0;i:65351;}i:65320;a:1:{i:0;i:65352;}i:65321;a:1:{i:0;i:65353;}i:65322;a:1:{i:0;i:65354;}i:65323;a:1:{i:0;i:65355;}i:65324;a:1:{i:0;i:65356;}i:65325;a:1:{i:0;i:65357;}i:65326;a:1:{i:0;i:65358;}i:65327;a:1:{i:0;i:65359;}i:65328;a:1:{i:0;i:65360;}i:65329;a:1:{i:0;i:65361;}i:65330;a:1:{i:0;i:65362;}i:65331;a:1:{i:0;i:65363;}i:65332;a:1:{i:0;i:65364;}i:65333;a:1:{i:0;i:65365;}i:65334;a:1:{i:0;i:65366;}i:65335;a:1:{i:0;i:65367;}i:65336;a:1:{i:0;i:65368;}i:65337;a:1:{i:0;i:65369;}i:65338;a:1:{i:0;i:65370;}i:66560;a:1:{i:0;i:66600;}i:66561;a:1:{i:0;i:66601;}i:66562;a:1:{i:0;i:66602;}i:66563;a:1:{i:0;i:66603;}i:66564;a:1:{i:0;i:66604;}i:66565;a:1:{i:0;i:66605;}i:66566;a:1:{i:0;i:66606;}i:66567;a:1:{i:0;i:66607;}i:66568;a:1:{i:0;i:66608;}i:66569;a:1:{i:0;i:66609;}i:66570;a:1:{i:0;i:66610;}i:66571;a:1:{i:0;i:66611;}i:66572;a:1:{i:0;i:66612;}i:66573;a:1:{i:0;i:66613;}i:66574;a:1:{i:0;i:66614;}i:66575;a:1:{i:0;i:66615;}i:66576;a:1:{i:0;i:66616;}i:66577;a:1:{i:0;i:66617;}i:66578;a:1:{i:0;i:66618;}i:66579;a:1:{i:0;i:66619;}i:66580;a:1:{i:0;i:66620;}i:66581;a:1:{i:0;i:66621;}i:66582;a:1:{i:0;i:66622;}i:66583;a:1:{i:0;i:66623;}i:66584;a:1:{i:0;i:66624;}i:66585;a:1:{i:0;i:66625;}i:66586;a:1:{i:0;i:66626;}i:66587;a:1:{i:0;i:66627;}i:66588;a:1:{i:0;i:66628;}i:66589;a:1:{i:0;i:66629;}i:66590;a:1:{i:0;i:66630;}i:66591;a:1:{i:0;i:66631;}i:66592;a:1:{i:0;i:66632;}i:66593;a:1:{i:0;i:66633;}i:66594;a:1:{i:0;i:66634;}i:66595;a:1:{i:0;i:66635;}i:66596;a:1:{i:0;i:66636;}i:66597;a:1:{i:0;i:66637;}i:119808;a:1:{i:0;i:97;}i:119809;a:1:{i:0;i:98;}i:119810;a:1:{i:0;i:99;}i:119811;a:1:{i:0;i:100;}i:119812;a:1:{i:0;i:101;}i:119813;a:1:{i:0;i:102;}i:119814;a:1:{i:0;i:103;}i:119815;a:1:{i:0;i:104;}i:119816;a:1:{i:0;i:105;}i:119817;a:1:{i:0;i:106;}i:119818;a:1:{i:0;i:107;}i:119819;a:1:{i:0;i:108;}i:119820;a:1:{i:0;i:109;}i:119821;a:1:{i:0;i:110;}i:119822;a:1:{i:0;i:111;}i:119823;a:1:{i:0;i:112;}i:119824;a:1:{i:0;i:113;}i:119825;a:1:{i:0;i:114;}i:119826;a:1:{i:0;i:115;}i:119827;a:1:{i:0;i:116;}i:119828;a:1:{i:0;i:117;}i:119829;a:1:{i:0;i:118;}i:119830;a:1:{i:0;i:119;}i:119831;a:1:{i:0;i:120;}i:119832;a:1:{i:0;i:121;}i:119833;a:1:{i:0;i:122;}i:119860;a:1:{i:0;i:97;}i:119861;a:1:{i:0;i:98;}i:119862;a:1:{i:0;i:99;}i:119863;a:1:{i:0;i:100;}i:119864;a:1:{i:0;i:101;}i:119865;a:1:{i:0;i:102;}i:119866;a:1:{i:0;i:103;}i:119867;a:1:{i:0;i:104;}i:119868;a:1:{i:0;i:105;}i:119869;a:1:{i:0;i:106;}i:119870;a:1:{i:0;i:107;}i:119871;a:1:{i:0;i:108;}i:119872;a:1:{i:0;i:109;}i:119873;a:1:{i:0;i:110;}i:119874;a:1:{i:0;i:111;}i:119875;a:1:{i:0;i:112;}i:119876;a:1:{i:0;i:113;}i:119877;a:1:{i:0;i:114;}i:119878;a:1:{i:0;i:115;}i:119879;a:1:{i:0;i:116;}i:119880;a:1:{i:0;i:117;}i:119881;a:1:{i:0;i:118;}i:119882;a:1:{i:0;i:119;}i:119883;a:1:{i:0;i:120;}i:119884;a:1:{i:0;i:121;}i:119885;a:1:{i:0;i:122;}i:119912;a:1:{i:0;i:97;}i:119913;a:1:{i:0;i:98;}i:119914;a:1:{i:0;i:99;}i:119915;a:1:{i:0;i:100;}i:119916;a:1:{i:0;i:101;}i:119917;a:1:{i:0;i:102;}i:119918;a:1:{i:0;i:103;}i:119919;a:1:{i:0;i:104;}i:119920;a:1:{i:0;i:105;}i:119921;a:1:{i:0;i:106;}i:119922;a:1:{i:0;i:107;}i:119923;a:1:{i:0;i:108;}i:119924;a:1:{i:0;i:109;}i:119925;a:1:{i:0;i:110;}i:119926;a:1:{i:0;i:111;}i:119927;a:1:{i:0;i:112;}i:119928;a:1:{i:0;i:113;}i:119929;a:1:{i:0;i:114;}i:119930;a:1:{i:0;i:115;}i:119931;a:1:{i:0;i:116;}i:119932;a:1:{i:0;i:117;}i:119933;a:1:{i:0;i:118;}i:119934;a:1:{i:0;i:119;}i:119935;a:1:{i:0;i:120;}i:119936;a:1:{i:0;i:121;}i:119937;a:1:{i:0;i:122;}i:119964;a:1:{i:0;i:97;}i:119966;a:1:{i:0;i:99;}i:119967;a:1:{i:0;i:100;}i:119970;a:1:{i:0;i:103;}i:119973;a:1:{i:0;i:106;}i:119974;a:1:{i:0;i:107;}i:119977;a:1:{i:0;i:110;}i:119978;a:1:{i:0;i:111;}i:119979;a:1:{i:0;i:112;}i:119980;a:1:{i:0;i:113;}i:119982;a:1:{i:0;i:115;}i:119983;a:1:{i:0;i:116;}i:119984;a:1:{i:0;i:117;}i:119985;a:1:{i:0;i:118;}i:119986;a:1:{i:0;i:119;}i:119987;a:1:{i:0;i:120;}i:119988;a:1:{i:0;i:121;}i:119989;a:1:{i:0;i:122;}i:120016;a:1:{i:0;i:97;}i:120017;a:1:{i:0;i:98;}i:120018;a:1:{i:0;i:99;}i:120019;a:1:{i:0;i:100;}i:120020;a:1:{i:0;i:101;}i:120021;a:1:{i:0;i:102;}i:120022;a:1:{i:0;i:103;}i:120023;a:1:{i:0;i:104;}i:120024;a:1:{i:0;i:105;}i:120025;a:1:{i:0;i:106;}i:120026;a:1:{i:0;i:107;}i:120027;a:1:{i:0;i:108;}i:120028;a:1:{i:0;i:109;}i:120029;a:1:{i:0;i:110;}i:120030;a:1:{i:0;i:111;}i:120031;a:1:{i:0;i:112;}i:120032;a:1:{i:0;i:113;}i:120033;a:1:{i:0;i:114;}i:120034;a:1:{i:0;i:115;}i:120035;a:1:{i:0;i:116;}i:120036;a:1:{i:0;i:117;}i:120037;a:1:{i:0;i:118;}i:120038;a:1:{i:0;i:119;}i:120039;a:1:{i:0;i:120;}i:120040;a:1:{i:0;i:121;}i:120041;a:1:{i:0;i:122;}i:120068;a:1:{i:0;i:97;}i:120069;a:1:{i:0;i:98;}i:120071;a:1:{i:0;i:100;}i:120072;a:1:{i:0;i:101;}i:120073;a:1:{i:0;i:102;}i:120074;a:1:{i:0;i:103;}i:120077;a:1:{i:0;i:106;}i:120078;a:1:{i:0;i:107;}i:120079;a:1:{i:0;i:108;}i:120080;a:1:{i:0;i:109;}i:120081;a:1:{i:0;i:110;}i:120082;a:1:{i:0;i:111;}i:120083;a:1:{i:0;i:112;}i:120084;a:1:{i:0;i:113;}i:120086;a:1:{i:0;i:115;}i:120087;a:1:{i:0;i:116;}i:120088;a:1:{i:0;i:117;}i:120089;a:1:{i:0;i:118;}i:120090;a:1:{i:0;i:119;}i:120091;a:1:{i:0;i:120;}i:120092;a:1:{i:0;i:121;}i:120120;a:1:{i:0;i:97;}i:120121;a:1:{i:0;i:98;}i:120123;a:1:{i:0;i:100;}i:120124;a:1:{i:0;i:101;}i:120125;a:1:{i:0;i:102;}i:120126;a:1:{i:0;i:103;}i:120128;a:1:{i:0;i:105;}i:120129;a:1:{i:0;i:106;}i:120130;a:1:{i:0;i:107;}i:120131;a:1:{i:0;i:108;}i:120132;a:1:{i:0;i:109;}i:120134;a:1:{i:0;i:111;}i:120138;a:1:{i:0;i:115;}i:120139;a:1:{i:0;i:116;}i:120140;a:1:{i:0;i:117;}i:120141;a:1:{i:0;i:118;}i:120142;a:1:{i:0;i:119;}i:120143;a:1:{i:0;i:120;}i:120144;a:1:{i:0;i:121;}i:120172;a:1:{i:0;i:97;}i:120173;a:1:{i:0;i:98;}i:120174;a:1:{i:0;i:99;}i:120175;a:1:{i:0;i:100;}i:120176;a:1:{i:0;i:101;}i:120177;a:1:{i:0;i:102;}i:120178;a:1:{i:0;i:103;}i:120179;a:1:{i:0;i:104;}i:120180;a:1:{i:0;i:105;}i:120181;a:1:{i:0;i:106;}i:120182;a:1:{i:0;i:107;}i:120183;a:1:{i:0;i:108;}i:120184;a:1:{i:0;i:109;}i:120185;a:1:{i:0;i:110;}i:120186;a:1:{i:0;i:111;}i:120187;a:1:{i:0;i:112;}i:120188;a:1:{i:0;i:113;}i:120189;a:1:{i:0;i:114;}i:120190;a:1:{i:0;i:115;}i:120191;a:1:{i:0;i:116;}i:120192;a:1:{i:0;i:117;}i:120193;a:1:{i:0;i:118;}i:120194;a:1:{i:0;i:119;}i:120195;a:1:{i:0;i:120;}i:120196;a:1:{i:0;i:121;}i:120197;a:1:{i:0;i:122;}i:120224;a:1:{i:0;i:97;}i:120225;a:1:{i:0;i:98;}i:120226;a:1:{i:0;i:99;}i:120227;a:1:{i:0;i:100;}i:120228;a:1:{i:0;i:101;}i:120229;a:1:{i:0;i:102;}i:120230;a:1:{i:0;i:103;}i:120231;a:1:{i:0;i:104;}i:120232;a:1:{i:0;i:105;}i:120233;a:1:{i:0;i:106;}i:120234;a:1:{i:0;i:107;}i:120235;a:1:{i:0;i:108;}i:120236;a:1:{i:0;i:109;}i:120237;a:1:{i:0;i:110;}i:120238;a:1:{i:0;i:111;}i:120239;a:1:{i:0;i:112;}i:120240;a:1:{i:0;i:113;}i:120241;a:1:{i:0;i:114;}i:120242;a:1:{i:0;i:115;}i:120243;a:1:{i:0;i:116;}i:120244;a:1:{i:0;i:117;}i:120245;a:1:{i:0;i:118;}i:120246;a:1:{i:0;i:119;}i:120247;a:1:{i:0;i:120;}i:120248;a:1:{i:0;i:121;}i:120249;a:1:{i:0;i:122;}i:120276;a:1:{i:0;i:97;}i:120277;a:1:{i:0;i:98;}i:120278;a:1:{i:0;i:99;}i:120279;a:1:{i:0;i:100;}i:120280;a:1:{i:0;i:101;}i:120281;a:1:{i:0;i:102;}i:120282;a:1:{i:0;i:103;}i:120283;a:1:{i:0;i:104;}i:120284;a:1:{i:0;i:105;}i:120285;a:1:{i:0;i:106;}i:120286;a:1:{i:0;i:107;}i:120287;a:1:{i:0;i:108;}i:120288;a:1:{i:0;i:109;}i:120289;a:1:{i:0;i:110;}i:120290;a:1:{i:0;i:111;}i:120291;a:1:{i:0;i:112;}i:120292;a:1:{i:0;i:113;}i:120293;a:1:{i:0;i:114;}i:120294;a:1:{i:0;i:115;}i:120295;a:1:{i:0;i:116;}i:120296;a:1:{i:0;i:117;}i:120297;a:1:{i:0;i:118;}i:120298;a:1:{i:0;i:119;}i:120299;a:1:{i:0;i:120;}i:120300;a:1:{i:0;i:121;}i:120301;a:1:{i:0;i:122;}i:120328;a:1:{i:0;i:97;}i:120329;a:1:{i:0;i:98;}i:120330;a:1:{i:0;i:99;}i:120331;a:1:{i:0;i:100;}i:120332;a:1:{i:0;i:101;}i:120333;a:1:{i:0;i:102;}i:120334;a:1:{i:0;i:103;}i:120335;a:1:{i:0;i:104;}i:120336;a:1:{i:0;i:105;}i:120337;a:1:{i:0;i:106;}i:120338;a:1:{i:0;i:107;}i:120339;a:1:{i:0;i:108;}i:120340;a:1:{i:0;i:109;}i:120341;a:1:{i:0;i:110;}i:120342;a:1:{i:0;i:111;}i:120343;a:1:{i:0;i:112;}i:120344;a:1:{i:0;i:113;}i:120345;a:1:{i:0;i:114;}i:120346;a:1:{i:0;i:115;}i:120347;a:1:{i:0;i:116;}i:120348;a:1:{i:0;i:117;}i:120349;a:1:{i:0;i:118;}i:120350;a:1:{i:0;i:119;}i:120351;a:1:{i:0;i:120;}i:120352;a:1:{i:0;i:121;}i:120353;a:1:{i:0;i:122;}i:120380;a:1:{i:0;i:97;}i:120381;a:1:{i:0;i:98;}i:120382;a:1:{i:0;i:99;}i:120383;a:1:{i:0;i:100;}i:120384;a:1:{i:0;i:101;}i:120385;a:1:{i:0;i:102;}i:120386;a:1:{i:0;i:103;}i:120387;a:1:{i:0;i:104;}i:120388;a:1:{i:0;i:105;}i:120389;a:1:{i:0;i:106;}i:120390;a:1:{i:0;i:107;}i:120391;a:1:{i:0;i:108;}i:120392;a:1:{i:0;i:109;}i:120393;a:1:{i:0;i:110;}i:120394;a:1:{i:0;i:111;}i:120395;a:1:{i:0;i:112;}i:120396;a:1:{i:0;i:113;}i:120397;a:1:{i:0;i:114;}i:120398;a:1:{i:0;i:115;}i:120399;a:1:{i:0;i:116;}i:120400;a:1:{i:0;i:117;}i:120401;a:1:{i:0;i:118;}i:120402;a:1:{i:0;i:119;}i:120403;a:1:{i:0;i:120;}i:120404;a:1:{i:0;i:121;}i:120405;a:1:{i:0;i:122;}i:120432;a:1:{i:0;i:97;}i:120433;a:1:{i:0;i:98;}i:120434;a:1:{i:0;i:99;}i:120435;a:1:{i:0;i:100;}i:120436;a:1:{i:0;i:101;}i:120437;a:1:{i:0;i:102;}i:120438;a:1:{i:0;i:103;}i:120439;a:1:{i:0;i:104;}i:120440;a:1:{i:0;i:105;}i:120441;a:1:{i:0;i:106;}i:120442;a:1:{i:0;i:107;}i:120443;a:1:{i:0;i:108;}i:120444;a:1:{i:0;i:109;}i:120445;a:1:{i:0;i:110;}i:120446;a:1:{i:0;i:111;}i:120447;a:1:{i:0;i:112;}i:120448;a:1:{i:0;i:113;}i:120449;a:1:{i:0;i:114;}i:120450;a:1:{i:0;i:115;}i:120451;a:1:{i:0;i:116;}i:120452;a:1:{i:0;i:117;}i:120453;a:1:{i:0;i:118;}i:120454;a:1:{i:0;i:119;}i:120455;a:1:{i:0;i:120;}i:120456;a:1:{i:0;i:121;}i:120457;a:1:{i:0;i:122;}i:120488;a:1:{i:0;i:945;}i:120489;a:1:{i:0;i:946;}i:120490;a:1:{i:0;i:947;}i:120491;a:1:{i:0;i:948;}i:120492;a:1:{i:0;i:949;}i:120493;a:1:{i:0;i:950;}i:120494;a:1:{i:0;i:951;}i:120495;a:1:{i:0;i:952;}i:120496;a:1:{i:0;i:953;}i:120497;a:1:{i:0;i:954;}i:120498;a:1:{i:0;i:955;}i:120499;a:1:{i:0;i:956;}i:120500;a:1:{i:0;i:957;}i:120501;a:1:{i:0;i:958;}i:120502;a:1:{i:0;i:959;}i:120503;a:1:{i:0;i:960;}i:120504;a:1:{i:0;i:961;}i:120505;a:1:{i:0;i:952;}i:120506;a:1:{i:0;i:963;}i:120507;a:1:{i:0;i:964;}i:120508;a:1:{i:0;i:965;}i:120509;a:1:{i:0;i:966;}i:120510;a:1:{i:0;i:967;}i:120511;a:1:{i:0;i:968;}i:120512;a:1:{i:0;i:969;}i:120531;a:1:{i:0;i:963;}i:120546;a:1:{i:0;i:945;}i:120547;a:1:{i:0;i:946;}i:120548;a:1:{i:0;i:947;}i:120549;a:1:{i:0;i:948;}i:120550;a:1:{i:0;i:949;}i:120551;a:1:{i:0;i:950;}i:120552;a:1:{i:0;i:951;}i:120553;a:1:{i:0;i:952;}i:120554;a:1:{i:0;i:953;}i:120555;a:1:{i:0;i:954;}i:120556;a:1:{i:0;i:955;}i:120557;a:1:{i:0;i:956;}i:120558;a:1:{i:0;i:957;}i:120559;a:1:{i:0;i:958;}i:120560;a:1:{i:0;i:959;}i:120561;a:1:{i:0;i:960;}i:120562;a:1:{i:0;i:961;}i:120563;a:1:{i:0;i:952;}i:120564;a:1:{i:0;i:963;}i:120565;a:1:{i:0;i:964;}i:120566;a:1:{i:0;i:965;}i:120567;a:1:{i:0;i:966;}i:120568;a:1:{i:0;i:967;}i:120569;a:1:{i:0;i:968;}i:120570;a:1:{i:0;i:969;}i:120589;a:1:{i:0;i:963;}i:120604;a:1:{i:0;i:945;}i:120605;a:1:{i:0;i:946;}i:120606;a:1:{i:0;i:947;}i:120607;a:1:{i:0;i:948;}i:120608;a:1:{i:0;i:949;}i:120609;a:1:{i:0;i:950;}i:120610;a:1:{i:0;i:951;}i:120611;a:1:{i:0;i:952;}i:120612;a:1:{i:0;i:953;}i:120613;a:1:{i:0;i:954;}i:120614;a:1:{i:0;i:955;}i:120615;a:1:{i:0;i:956;}i:120616;a:1:{i:0;i:957;}i:120617;a:1:{i:0;i:958;}i:120618;a:1:{i:0;i:959;}i:120619;a:1:{i:0;i:960;}i:120620;a:1:{i:0;i:961;}i:120621;a:1:{i:0;i:952;}i:120622;a:1:{i:0;i:963;}i:120623;a:1:{i:0;i:964;}i:120624;a:1:{i:0;i:965;}i:120625;a:1:{i:0;i:966;}i:120626;a:1:{i:0;i:967;}i:120627;a:1:{i:0;i:968;}i:120628;a:1:{i:0;i:969;}i:120647;a:1:{i:0;i:963;}i:120662;a:1:{i:0;i:945;}i:120663;a:1:{i:0;i:946;}i:120664;a:1:{i:0;i:947;}i:120665;a:1:{i:0;i:948;}i:120666;a:1:{i:0;i:949;}i:120667;a:1:{i:0;i:950;}i:120668;a:1:{i:0;i:951;}i:120669;a:1:{i:0;i:952;}i:120670;a:1:{i:0;i:953;}i:120671;a:1:{i:0;i:954;}i:120672;a:1:{i:0;i:955;}i:120673;a:1:{i:0;i:956;}i:120674;a:1:{i:0;i:957;}i:120675;a:1:{i:0;i:958;}i:120676;a:1:{i:0;i:959;}i:120677;a:1:{i:0;i:960;}i:120678;a:1:{i:0;i:961;}i:120679;a:1:{i:0;i:952;}i:120680;a:1:{i:0;i:963;}i:120681;a:1:{i:0;i:964;}i:120682;a:1:{i:0;i:965;}i:120683;a:1:{i:0;i:966;}i:120684;a:1:{i:0;i:967;}i:120685;a:1:{i:0;i:968;}i:120686;a:1:{i:0;i:969;}i:120705;a:1:{i:0;i:963;}i:120720;a:1:{i:0;i:945;}i:120721;a:1:{i:0;i:946;}i:120722;a:1:{i:0;i:947;}i:120723;a:1:{i:0;i:948;}i:120724;a:1:{i:0;i:949;}i:120725;a:1:{i:0;i:950;}i:120726;a:1:{i:0;i:951;}i:120727;a:1:{i:0;i:952;}i:120728;a:1:{i:0;i:953;}i:120729;a:1:{i:0;i:954;}i:120730;a:1:{i:0;i:955;}i:120731;a:1:{i:0;i:956;}i:120732;a:1:{i:0;i:957;}i:120733;a:1:{i:0;i:958;}i:120734;a:1:{i:0;i:959;}i:120735;a:1:{i:0;i:960;}i:120736;a:1:{i:0;i:961;}i:120737;a:1:{i:0;i:952;}i:120738;a:1:{i:0;i:963;}i:120739;a:1:{i:0;i:964;}i:120740;a:1:{i:0;i:965;}i:120741;a:1:{i:0;i:966;}i:120742;a:1:{i:0;i:967;}i:120743;a:1:{i:0;i:968;}i:120744;a:1:{i:0;i:969;}i:120763;a:1:{i:0;i:963;}i:1017;a:1:{i:0;i:963;}i:7468;a:1:{i:0;i:97;}i:7469;a:1:{i:0;i:230;}i:7470;a:1:{i:0;i:98;}i:7472;a:1:{i:0;i:100;}i:7473;a:1:{i:0;i:101;}i:7474;a:1:{i:0;i:477;}i:7475;a:1:{i:0;i:103;}i:7476;a:1:{i:0;i:104;}i:7477;a:1:{i:0;i:105;}i:7478;a:1:{i:0;i:106;}i:7479;a:1:{i:0;i:107;}i:7480;a:1:{i:0;i:108;}i:7481;a:1:{i:0;i:109;}i:7482;a:1:{i:0;i:110;}i:7484;a:1:{i:0;i:111;}i:7485;a:1:{i:0;i:547;}i:7486;a:1:{i:0;i:112;}i:7487;a:1:{i:0;i:114;}i:7488;a:1:{i:0;i:116;}i:7489;a:1:{i:0;i:117;}i:7490;a:1:{i:0;i:119;}i:8507;a:3:{i:0;i:102;i:1;i:97;i:2;i:120;}i:12880;a:3:{i:0;i:112;i:1;i:116;i:2;i:101;}i:13004;a:2:{i:0;i:104;i:1;i:103;}i:13006;a:2:{i:0;i:101;i:1;i:118;}i:13007;a:3:{i:0;i:108;i:1;i:116;i:2;i:100;}i:13178;a:2:{i:0;i:105;i:1;i:117;}i:13278;a:3:{i:0;i:118;i:1;i:8725;i:2;i:109;}i:13279;a:3:{i:0;i:97;i:1;i:8725;i:2;i:109;}}s:12:"norm_combcls";a:341:{i:820;i:1;i:821;i:1;i:822;i:1;i:823;i:1;i:824;i:1;i:2364;i:7;i:2492;i:7;i:2620;i:7;i:2748;i:7;i:2876;i:7;i:3260;i:7;i:4151;i:7;i:12441;i:8;i:12442;i:8;i:2381;i:9;i:2509;i:9;i:2637;i:9;i:2765;i:9;i:2893;i:9;i:3021;i:9;i:3149;i:9;i:3277;i:9;i:3405;i:9;i:3530;i:9;i:3642;i:9;i:3972;i:9;i:4153;i:9;i:5908;i:9;i:5940;i:9;i:6098;i:9;i:1456;i:10;i:1457;i:11;i:1458;i:12;i:1459;i:13;i:1460;i:14;i:1461;i:15;i:1462;i:16;i:1463;i:17;i:1464;i:18;i:1465;i:19;i:1467;i:20;i:1468;i:21;i:1469;i:22;i:1471;i:23;i:1473;i:24;i:1474;i:25;i:64286;i:26;i:1611;i:27;i:1612;i:28;i:1613;i:29;i:1614;i:30;i:1615;i:31;i:1616;i:32;i:1617;i:33;i:1618;i:34;i:1648;i:35;i:1809;i:36;i:3157;i:84;i:3158;i:91;i:3640;i:103;i:3641;i:103;i:3656;i:107;i:3657;i:107;i:3658;i:107;i:3659;i:107;i:3768;i:118;i:3769;i:118;i:3784;i:122;i:3785;i:122;i:3786;i:122;i:3787;i:122;i:3953;i:129;i:3954;i:130;i:3962;i:130;i:3963;i:130;i:3964;i:130;i:3965;i:130;i:3968;i:130;i:3956;i:132;i:801;i:202;i:802;i:202;i:807;i:202;i:808;i:202;i:795;i:216;i:3897;i:216;i:119141;i:216;i:119142;i:216;i:119150;i:216;i:119151;i:216;i:119152;i:216;i:119153;i:216;i:119154;i:216;i:12330;i:218;i:790;i:220;i:791;i:220;i:792;i:220;i:793;i:220;i:796;i:220;i:797;i:220;i:798;i:220;i:799;i:220;i:800;i:220;i:803;i:220;i:804;i:220;i:805;i:220;i:806;i:220;i:809;i:220;i:810;i:220;i:811;i:220;i:812;i:220;i:813;i:220;i:814;i:220;i:815;i:220;i:816;i:220;i:817;i:220;i:818;i:220;i:819;i:220;i:825;i:220;i:826;i:220;i:827;i:220;i:828;i:220;i:839;i:220;i:840;i:220;i:841;i:220;i:845;i:220;i:846;i:220;i:851;i:220;i:852;i:220;i:853;i:220;i:854;i:220;i:1425;i:220;i:1430;i:220;i:1435;i:220;i:1443;i:220;i:1444;i:220;i:1445;i:220;i:1446;i:220;i:1447;i:220;i:1450;i:220;i:1621;i:220;i:1622;i:220;i:1763;i:220;i:1770;i:220;i:1773;i:220;i:1841;i:220;i:1844;i:220;i:1847;i:220;i:1848;i:220;i:1849;i:220;i:1851;i:220;i:1852;i:220;i:1854;i:220;i:1858;i:220;i:1860;i:220;i:1862;i:220;i:1864;i:220;i:2386;i:220;i:3864;i:220;i:3865;i:220;i:3893;i:220;i:3895;i:220;i:4038;i:220;i:6459;i:220;i:8424;i:220;i:119163;i:220;i:119164;i:220;i:119165;i:220;i:119166;i:220;i:119167;i:220;i:119168;i:220;i:119169;i:220;i:119170;i:220;i:119178;i:220;i:119179;i:220;i:1434;i:222;i:1453;i:222;i:6441;i:222;i:12333;i:222;i:12334;i:224;i:12335;i:224;i:119149;i:226;i:1454;i:228;i:6313;i:228;i:12331;i:228;i:768;i:230;i:769;i:230;i:770;i:230;i:771;i:230;i:772;i:230;i:773;i:230;i:774;i:230;i:775;i:230;i:776;i:230;i:777;i:230;i:778;i:230;i:779;i:230;i:780;i:230;i:781;i:230;i:782;i:230;i:783;i:230;i:784;i:230;i:785;i:230;i:786;i:230;i:787;i:230;i:788;i:230;i:829;i:230;i:830;i:230;i:831;i:230;i:832;i:230;i:833;i:230;i:834;i:230;i:835;i:230;i:836;i:230;i:838;i:230;i:842;i:230;i:843;i:230;i:844;i:230;i:848;i:230;i:849;i:230;i:850;i:230;i:855;i:230;i:867;i:230;i:868;i:230;i:869;i:230;i:870;i:230;i:871;i:230;i:872;i:230;i:873;i:230;i:874;i:230;i:875;i:230;i:876;i:230;i:877;i:230;i:878;i:230;i:879;i:230;i:1155;i:230;i:1156;i:230;i:1157;i:230;i:1158;i:230;i:1426;i:230;i:1427;i:230;i:1428;i:230;i:1429;i:230;i:1431;i:230;i:1432;i:230;i:1433;i:230;i:1436;i:230;i:1437;i:230;i:1438;i:230;i:1439;i:230;i:1440;i:230;i:1441;i:230;i:1448;i:230;i:1449;i:230;i:1451;i:230;i:1452;i:230;i:1455;i:230;i:1476;i:230;i:1552;i:230;i:1553;i:230;i:1554;i:230;i:1555;i:230;i:1556;i:230;i:1557;i:230;i:1619;i:230;i:1620;i:230;i:1623;i:230;i:1624;i:230;i:1750;i:230;i:1751;i:230;i:1752;i:230;i:1753;i:230;i:1754;i:230;i:1755;i:230;i:1756;i:230;i:1759;i:230;i:1760;i:230;i:1761;i:230;i:1762;i:230;i:1764;i:230;i:1767;i:230;i:1768;i:230;i:1771;i:230;i:1772;i:230;i:1840;i:230;i:1842;i:230;i:1843;i:230;i:1845;i:230;i:1846;i:230;i:1850;i:230;i:1853;i:230;i:1855;i:230;i:1856;i:230;i:1857;i:230;i:1859;i:230;i:1861;i:230;i:1863;i:230;i:1865;i:230;i:1866;i:230;i:2385;i:230;i:2387;i:230;i:2388;i:230;i:3970;i:230;i:3971;i:230;i:3974;i:230;i:3975;i:230;i:5901;i:230;i:6458;i:230;i:8400;i:230;i:8401;i:230;i:8404;i:230;i:8405;i:230;i:8406;i:230;i:8407;i:230;i:8411;i:230;i:8412;i:230;i:8417;i:230;i:8423;i:230;i:8425;i:230;i:65056;i:230;i:65057;i:230;i:65058;i:230;i:65059;i:230;i:119173;i:230;i:119174;i:230;i:119175;i:230;i:119177;i:230;i:119176;i:230;i:119210;i:230;i:119211;i:230;i:119212;i:230;i:119213;i:230;i:789;i:232;i:794;i:232;i:12332;i:232;i:863;i:233;i:866;i:233;i:861;i:234;i:862;i:234;i:864;i:234;i:865;i:234;i:837;i:240;}} \ No newline at end of file diff --git a/library/simplepie/simplepie.inc b/library/simplepie/simplepie.inc deleted file mode 100644 index 96ad06678e..0000000000 --- a/library/simplepie/simplepie.inc +++ /dev/null @@ -1,15150 +0,0 @@ -<?php -/** - * SimplePie - * - * A PHP-Based RSS and Atom Feed Framework. - * Takes the hard work out of managing a complete RSS/Atom solution. - * - * Copyright (c) 2004-2009, Ryan Parman and Geoffrey Sneddon - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * * Neither the name of the SimplePie Team nor the names of its contributors may be used - * to endorse or promote products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS - * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @package SimplePie - * @version 1.2.1-dev - * @copyright 2004-2009 Ryan Parman, Geoffrey Sneddon - * @author Ryan Parman - * @author Geoffrey Sneddon - * @link http://simplepie.org/ SimplePie - * @link http://simplepie.org/support/ Please submit all bug reports and feature requests to the SimplePie forums - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @todo phpDoc comments - */ - -/** - * SimplePie Name - */ -define('SIMPLEPIE_NAME', 'SimplePie'); - -/** - * SimplePie Version - */ -define('SIMPLEPIE_VERSION', '1.2.1-dev'); - -/** - * SimplePie Build - * @todo Hardcode for release (there's no need to have to call SimplePie_Misc::parse_date() only every load of simplepie.inc) - */ -define('SIMPLEPIE_BUILD', gmdate('YmdHis', SimplePie_Misc::parse_date(substr('$Date$', 7, 25)) ? SimplePie_Misc::parse_date(substr('$Date$', 7, 25)) : filemtime(__FILE__))); - -/** - * SimplePie Website URL - */ -define('SIMPLEPIE_URL', 'http://simplepie.org'); - -/** - * SimplePie Useragent - * @see SimplePie::set_useragent() - */ -define('SIMPLEPIE_USERAGENT', SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION . ' (Feed Parser; ' . SIMPLEPIE_URL . '; Allow like Gecko) Build/' . SIMPLEPIE_BUILD); - -/** - * SimplePie Linkback - */ -define('SIMPLEPIE_LINKBACK', '<a href="' . SIMPLEPIE_URL . '" title="' . SIMPLEPIE_NAME . ' ' . SIMPLEPIE_VERSION . '">' . SIMPLEPIE_NAME . '</a>'); - -/** - * No Autodiscovery - * @see SimplePie::set_autodiscovery_level() - */ -define('SIMPLEPIE_LOCATOR_NONE', 0); - -/** - * Feed Link Element Autodiscovery - * @see SimplePie::set_autodiscovery_level() - */ -define('SIMPLEPIE_LOCATOR_AUTODISCOVERY', 1); - -/** - * Local Feed Extension Autodiscovery - * @see SimplePie::set_autodiscovery_level() - */ -define('SIMPLEPIE_LOCATOR_LOCAL_EXTENSION', 2); - -/** - * Local Feed Body Autodiscovery - * @see SimplePie::set_autodiscovery_level() - */ -define('SIMPLEPIE_LOCATOR_LOCAL_BODY', 4); - -/** - * Remote Feed Extension Autodiscovery - * @see SimplePie::set_autodiscovery_level() - */ -define('SIMPLEPIE_LOCATOR_REMOTE_EXTENSION', 8); - -/** - * Remote Feed Body Autodiscovery - * @see SimplePie::set_autodiscovery_level() - */ -define('SIMPLEPIE_LOCATOR_REMOTE_BODY', 16); - -/** - * All Feed Autodiscovery - * @see SimplePie::set_autodiscovery_level() - */ -define('SIMPLEPIE_LOCATOR_ALL', 31); - -/** - * No known feed type - */ -define('SIMPLEPIE_TYPE_NONE', 0); - -/** - * RSS 0.90 - */ -define('SIMPLEPIE_TYPE_RSS_090', 1); - -/** - * RSS 0.91 (Netscape) - */ -define('SIMPLEPIE_TYPE_RSS_091_NETSCAPE', 2); - -/** - * RSS 0.91 (Userland) - */ -define('SIMPLEPIE_TYPE_RSS_091_USERLAND', 4); - -/** - * RSS 0.91 (both Netscape and Userland) - */ -define('SIMPLEPIE_TYPE_RSS_091', 6); - -/** - * RSS 0.92 - */ -define('SIMPLEPIE_TYPE_RSS_092', 8); - -/** - * RSS 0.93 - */ -define('SIMPLEPIE_TYPE_RSS_093', 16); - -/** - * RSS 0.94 - */ -define('SIMPLEPIE_TYPE_RSS_094', 32); - -/** - * RSS 1.0 - */ -define('SIMPLEPIE_TYPE_RSS_10', 64); - -/** - * RSS 2.0 - */ -define('SIMPLEPIE_TYPE_RSS_20', 128); - -/** - * RDF-based RSS - */ -define('SIMPLEPIE_TYPE_RSS_RDF', 65); - -/** - * Non-RDF-based RSS (truly intended as syndication format) - */ -define('SIMPLEPIE_TYPE_RSS_SYNDICATION', 190); - -/** - * All RSS - */ -define('SIMPLEPIE_TYPE_RSS_ALL', 255); - -/** - * Atom 0.3 - */ -define('SIMPLEPIE_TYPE_ATOM_03', 256); - -/** - * Atom 1.0 - */ -define('SIMPLEPIE_TYPE_ATOM_10', 512); - -/** - * All Atom - */ -define('SIMPLEPIE_TYPE_ATOM_ALL', 768); - -/** - * All feed types - */ -define('SIMPLEPIE_TYPE_ALL', 1023); - -/** - * No construct - */ -define('SIMPLEPIE_CONSTRUCT_NONE', 0); - -/** - * Text construct - */ -define('SIMPLEPIE_CONSTRUCT_TEXT', 1); - -/** - * HTML construct - */ -define('SIMPLEPIE_CONSTRUCT_HTML', 2); - -/** - * XHTML construct - */ -define('SIMPLEPIE_CONSTRUCT_XHTML', 4); - -/** - * base64-encoded construct - */ -define('SIMPLEPIE_CONSTRUCT_BASE64', 8); - -/** - * IRI construct - */ -define('SIMPLEPIE_CONSTRUCT_IRI', 16); - -/** - * A construct that might be HTML - */ -define('SIMPLEPIE_CONSTRUCT_MAYBE_HTML', 32); - -/** - * All constructs - */ -define('SIMPLEPIE_CONSTRUCT_ALL', 63); - -/** - * Don't change case - */ -define('SIMPLEPIE_SAME_CASE', 1); - -/** - * Change to lowercase - */ -define('SIMPLEPIE_LOWERCASE', 2); - -/** - * Change to uppercase - */ -define('SIMPLEPIE_UPPERCASE', 4); - -/** - * PCRE for HTML attributes - */ -define('SIMPLEPIE_PCRE_HTML_ATTRIBUTE', '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*'); - -/** - * PCRE for XML attributes - */ -define('SIMPLEPIE_PCRE_XML_ATTRIBUTE', '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*'); - -/** - * XML Namespace - */ -define('SIMPLEPIE_NAMESPACE_XML', 'http://www.w3.org/XML/1998/namespace'); - -/** - * Atom 1.0 Namespace - */ -define('SIMPLEPIE_NAMESPACE_ATOM_10', 'http://www.w3.org/2005/Atom'); - -/** - * Atom 0.3 Namespace - */ -define('SIMPLEPIE_NAMESPACE_ATOM_03', 'http://purl.org/atom/ns#'); - -/** - * RDF Namespace - */ -define('SIMPLEPIE_NAMESPACE_RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); - -/** - * RSS 0.90 Namespace - */ -define('SIMPLEPIE_NAMESPACE_RSS_090', 'http://my.netscape.com/rdf/simple/0.9/'); - -/** - * RSS 1.0 Namespace - */ -define('SIMPLEPIE_NAMESPACE_RSS_10', 'http://purl.org/rss/1.0/'); - -/** - * RSS 1.0 Content Module Namespace - */ -define('SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT', 'http://purl.org/rss/1.0/modules/content/'); - -/** - * RSS 2.0 Namespace - * (Stupid, I know, but I'm certain it will confuse people less with support.) - */ -define('SIMPLEPIE_NAMESPACE_RSS_20', ''); - -/** - * DC 1.0 Namespace - */ -define('SIMPLEPIE_NAMESPACE_DC_10', 'http://purl.org/dc/elements/1.0/'); - -/** - * DC 1.1 Namespace - */ -define('SIMPLEPIE_NAMESPACE_DC_11', 'http://purl.org/dc/elements/1.1/'); - -/** - * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace - */ -define('SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO', 'http://www.w3.org/2003/01/geo/wgs84_pos#'); - -/** - * GeoRSS Namespace - */ -define('SIMPLEPIE_NAMESPACE_GEORSS', 'http://www.georss.org/georss'); - -/** - * Media RSS Namespace - */ -define('SIMPLEPIE_NAMESPACE_MEDIARSS', 'http://search.yahoo.com/mrss/'); - -/** - * Wrong Media RSS Namespace - */ -define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG', 'http://search.yahoo.com/mrss'); - -/** - * iTunes RSS Namespace - */ -define('SIMPLEPIE_NAMESPACE_ITUNES', 'http://www.itunes.com/dtds/podcast-1.0.dtd'); - -/** - * XHTML Namespace - */ -define('SIMPLEPIE_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml'); - -/** - * IANA Link Relations Registry - */ -define('SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY', 'http://www.iana.org/assignments/relation/'); - -/** - * Whether we're running on PHP5 - */ -define('SIMPLEPIE_PHP5', version_compare(PHP_VERSION, '5.0.0', '>=')); - -/** - * No file source - */ -define('SIMPLEPIE_FILE_SOURCE_NONE', 0); - -/** - * Remote file source - */ -define('SIMPLEPIE_FILE_SOURCE_REMOTE', 1); - -/** - * Local file source - */ -define('SIMPLEPIE_FILE_SOURCE_LOCAL', 2); - -/** - * fsockopen() file source - */ -define('SIMPLEPIE_FILE_SOURCE_FSOCKOPEN', 4); - -/** - * cURL file source - */ -define('SIMPLEPIE_FILE_SOURCE_CURL', 8); - -/** - * file_get_contents() file source - */ -define('SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS', 16); - -/** - * SimplePie - * - * @package SimplePie - */ -class SimplePie -{ - /** - * @var array Raw data - * @access private - */ - var $data = array(); - - /** - * @var mixed Error string - * @access private - */ - var $error; - - /** - * @var object Instance of SimplePie_Sanitize (or other class) - * @see SimplePie::set_sanitize_class() - * @access private - */ - var $sanitize; - - /** - * @var string SimplePie Useragent - * @see SimplePie::set_useragent() - * @access private - */ - var $useragent = SIMPLEPIE_USERAGENT; - - /** - * @var string Feed URL - * @see SimplePie::set_feed_url() - * @access private - */ - var $feed_url; - - /** - * @var object Instance of SimplePie_File to use as a feed - * @see SimplePie::set_file() - * @access private - */ - var $file; - - /** - * @var string Raw feed data - * @see SimplePie::set_raw_data() - * @access private - */ - var $raw_data; - - /** - * @var int Timeout for fetching remote files - * @see SimplePie::set_timeout() - * @access private - */ - var $timeout = 10; - - /** - * @var bool Forces fsockopen() to be used for remote files instead - * of cURL, even if a new enough version is installed - * @see SimplePie::force_fsockopen() - * @access private - */ - var $force_fsockopen = false; - - /** - * @var bool Force the given data/URL to be treated as a feed no matter what - * it appears like - * @see SimplePie::force_feed() - * @access private - */ - var $force_feed = false; - - /** - * @var bool Enable/Disable XML dump - * @see SimplePie::enable_xml_dump() - * @access private - */ - var $xml_dump = false; - - /** - * @var bool Enable/Disable Caching - * @see SimplePie::enable_cache() - * @access private - */ - var $cache = true; - - /** - * @var int Cache duration (in seconds) - * @see SimplePie::set_cache_duration() - * @access private - */ - var $cache_duration = 3600; - - /** - * @var int Auto-discovery cache duration (in seconds) - * @see SimplePie::set_autodiscovery_cache_duration() - * @access private - */ - var $autodiscovery_cache_duration = 604800; // 7 Days. - - /** - * @var string Cache location (relative to executing script) - * @see SimplePie::set_cache_location() - * @access private - */ - var $cache_location = './cache'; - - /** - * @var string Function that creates the cache filename - * @see SimplePie::set_cache_name_function() - * @access private - */ - var $cache_name_function = 'md5'; - - /** - * @var bool Reorder feed by date descending - * @see SimplePie::enable_order_by_date() - * @access private - */ - var $order_by_date = true; - - /** - * @var mixed Force input encoding to be set to the follow value - * (false, or anything type-cast to false, disables this feature) - * @see SimplePie::set_input_encoding() - * @access private - */ - var $input_encoding = false; - - /** - * @var int Feed Autodiscovery Level - * @see SimplePie::set_autodiscovery_level() - * @access private - */ - var $autodiscovery = SIMPLEPIE_LOCATOR_ALL; - - /** - * @var string Class used for caching feeds - * @see SimplePie::set_cache_class() - * @access private - */ - var $cache_class = 'SimplePie_Cache'; - - /** - * @var string Class used for locating feeds - * @see SimplePie::set_locator_class() - * @access private - */ - var $locator_class = 'SimplePie_Locator'; - - /** - * @var string Class used for parsing feeds - * @see SimplePie::set_parser_class() - * @access private - */ - var $parser_class = 'SimplePie_Parser'; - - /** - * @var string Class used for fetching feeds - * @see SimplePie::set_file_class() - * @access private - */ - var $file_class = 'SimplePie_File'; - - /** - * @var string Class used for items - * @see SimplePie::set_item_class() - * @access private - */ - var $item_class = 'SimplePie_Item'; - - /** - * @var string Class used for authors - * @see SimplePie::set_author_class() - * @access private - */ - var $author_class = 'SimplePie_Author'; - - /** - * @var string Class used for categories - * @see SimplePie::set_category_class() - * @access private - */ - var $category_class = 'SimplePie_Category'; - - /** - * @var string Class used for enclosures - * @see SimplePie::set_enclosures_class() - * @access private - */ - var $enclosure_class = 'SimplePie_Enclosure'; - - /** - * @var string Class used for Media RSS <media:text> captions - * @see SimplePie::set_caption_class() - * @access private - */ - var $caption_class = 'SimplePie_Caption'; - - /** - * @var string Class used for Media RSS <media:copyright> - * @see SimplePie::set_copyright_class() - * @access private - */ - var $copyright_class = 'SimplePie_Copyright'; - - /** - * @var string Class used for Media RSS <media:credit> - * @see SimplePie::set_credit_class() - * @access private - */ - var $credit_class = 'SimplePie_Credit'; - - /** - * @var string Class used for Media RSS <media:rating> - * @see SimplePie::set_rating_class() - * @access private - */ - var $rating_class = 'SimplePie_Rating'; - - /** - * @var string Class used for Media RSS <media:restriction> - * @see SimplePie::set_restriction_class() - * @access private - */ - var $restriction_class = 'SimplePie_Restriction'; - - /** - * @var string Class used for content-type sniffing - * @see SimplePie::set_content_type_sniffer_class() - * @access private - */ - var $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer'; - - /** - * @var string Class used for item sources. - * @see SimplePie::set_source_class() - * @access private - */ - var $source_class = 'SimplePie_Source'; - - /** - * @var mixed Set javascript query string parameter (false, or - * anything type-cast to false, disables this feature) - * @see SimplePie::set_javascript() - * @access private - */ - var $javascript = 'js'; - - /** - * @var int Maximum number of feeds to check with autodiscovery - * @see SimplePie::set_max_checked_feeds() - * @access private - */ - var $max_checked_feeds = 10; - - /** - * @var array All the feeds found during the autodiscovery process - * @see SimplePie::get_all_discovered_feeds() - * @access private - */ - var $all_discovered_feeds = array(); - - /** - * @var string Web-accessible path to the handler_favicon.php file. - * @see SimplePie::set_favicon_handler() - * @access private - */ - var $favicon_handler = ''; - - /** - * @var string Web-accessible path to the handler_image.php file. - * @see SimplePie::set_image_handler() - * @access private - */ - var $image_handler = ''; - - /** - * @var array Stores the URLs when multiple feeds are being initialized. - * @see SimplePie::set_feed_url() - * @access private - */ - var $multifeed_url = array(); - - /** - * @var array Stores SimplePie objects when multiple feeds initialized. - * @access private - */ - var $multifeed_objects = array(); - - /** - * @var array Stores the get_object_vars() array for use with multifeeds. - * @see SimplePie::set_feed_url() - * @access private - */ - var $config_settings = null; - - /** - * @var integer Stores the number of items to return per-feed with multifeeds. - * @see SimplePie::set_item_limit() - * @access private - */ - var $item_limit = 0; - - /** - * @var array Stores the default attributes to be stripped by strip_attributes(). - * @see SimplePie::strip_attributes() - * @access private - */ - var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); - - /** - * @var array Stores the default tags to be stripped by strip_htmltags(). - * @see SimplePie::strip_htmltags() - * @access private - */ - var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); - - /** - * The SimplePie class contains feed level data and options - * - * There are two ways that you can create a new SimplePie object. The first - * is by passing a feed URL as a parameter to the SimplePie constructor - * (as well as optionally setting the cache location and cache expiry). This - * will initialise the whole feed with all of the default settings, and you - * can begin accessing methods and properties immediately. - * - * The second way is to create the SimplePie object with no parameters - * at all. This will enable you to set configuration options. After setting - * them, you must initialise the feed using $feed->init(). At that point the - * object's methods and properties will be available to you. This format is - * what is used throughout this documentation. - * - * @access public - * @since 1.0 Preview Release - * @param string $feed_url This is the URL you want to parse. - * @param string $cache_location This is where you want the cache to be stored. - * @param int $cache_duration This is the number of seconds that you want to store the cache file for. - */ - function SimplePie($feed_url = null, $cache_location = null, $cache_duration = null) - { - // Other objects, instances created here so we can set options on them - $this->sanitize = new SimplePie_Sanitize; - - // Set options if they're passed to the constructor - if ($cache_location !== null) - { - $this->set_cache_location($cache_location); - } - - if ($cache_duration !== null) - { - $this->set_cache_duration($cache_duration); - } - - // Only init the script if we're passed a feed URL - if ($feed_url !== null) - { - $this->set_feed_url($feed_url); - $this->init(); - } - } - - /** - * Used for converting object to a string - */ - function __toString() - { - return md5(serialize($this->data)); - } - - /** - * Remove items that link back to this before destroying this object - */ - function __destruct() - { - if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode')) - { - if (!empty($this->data['items'])) - { - foreach ($this->data['items'] as $item) - { - $item->__destruct(); - } - unset($item, $this->data['items']); - } - if (!empty($this->data['ordered_items'])) - { - foreach ($this->data['ordered_items'] as $item) - { - $item->__destruct(); - } - unset($item, $this->data['ordered_items']); - } - } - } - - /** - * Force the given data/URL to be treated as a feed no matter what it - * appears like - * - * @access public - * @since 1.1 - * @param bool $enable Force the given data/URL to be treated as a feed - */ - function force_feed($enable = false) - { - $this->force_feed = (bool) $enable; - } - - /** - * This is the URL of the feed you want to parse. - * - * This allows you to enter the URL of the feed you want to parse, or the - * website you want to try to use auto-discovery on. This takes priority - * over any set raw data. - * - * You can set multiple feeds to mash together by passing an array instead - * of a string for the $url. Remember that with each additional feed comes - * additional processing and resources. - * - * @access public - * @since 1.0 Preview Release - * @param mixed $url This is the URL (or array of URLs) that you want to parse. - * @see SimplePie::set_raw_data() - */ - function set_feed_url($url) - { - if (is_array($url)) - { - $this->multifeed_url = array(); - foreach ($url as $value) - { - $this->multifeed_url[] = SimplePie_Misc::fix_protocol($value, 1); - } - } - else - { - $this->feed_url = SimplePie_Misc::fix_protocol($url, 1); - } - } - - /** - * Provides an instance of SimplePie_File to use as a feed - * - * @access public - * @param object &$file Instance of SimplePie_File (or subclass) - * @return bool True on success, false on failure - */ - function set_file(&$file) - { - if (is_a($file, 'SimplePie_File')) - { - $this->feed_url = $file->url; - $this->file =& $file; - return true; - } - return false; - } - - /** - * Allows you to use a string of RSS/Atom data instead of a remote feed. - * - * If you have a feed available as a string in PHP, you can tell SimplePie - * to parse that data string instead of a remote feed. Any set feed URL - * takes precedence. - * - * @access public - * @since 1.0 Beta 3 - * @param string $data RSS or Atom data as a string. - * @see SimplePie::set_feed_url() - */ - function set_raw_data($data) - { - $this->raw_data = $data; - } - - /** - * Allows you to override the default timeout for fetching remote feeds. - * - * This allows you to change the maximum time the feed's server to respond - * and send the feed back. - * - * @access public - * @since 1.0 Beta 3 - * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed. - */ - function set_timeout($timeout = 10) - { - $this->timeout = (int) $timeout; - } - - /** - * Forces SimplePie to use fsockopen() instead of the preferred cURL - * functions. - * - * @access public - * @since 1.0 Beta 3 - * @param bool $enable Force fsockopen() to be used - */ - function force_fsockopen($enable = false) - { - $this->force_fsockopen = (bool) $enable; - } - - /** - * Outputs the raw XML content of the feed, after it has gone through - * SimplePie's filters. - * - * Used only for debugging, this function will output the XML content as - * text/xml. When SimplePie reads in a feed, it does a bit of cleaning up - * before trying to parse it. Many parts of the feed are re-written in - * memory, and in the end, you have a parsable feed. XML dump shows you the - * actual XML that SimplePie tries to parse, which may or may not be very - * different from the original feed. - * - * @access public - * @since 1.0 Preview Release - * @param bool $enable Enable XML dump - */ - function enable_xml_dump($enable = false) - { - $this->xml_dump = (bool) $enable; - } - - /** - * Enables/disables caching in SimplePie. - * - * This option allows you to disable caching all-together in SimplePie. - * However, disabling the cache can lead to longer load times. - * - * @access public - * @since 1.0 Preview Release - * @param bool $enable Enable caching - */ - function enable_cache($enable = true) - { - $this->cache = (bool) $enable; - } - - /** - * Set the length of time (in seconds) that the contents of a feed - * will be cached. - * - * @access public - * @param int $seconds The feed content cache duration. - */ - function set_cache_duration($seconds = 3600) - { - $this->cache_duration = (int) $seconds; - } - - /** - * Set the length of time (in seconds) that the autodiscovered feed - * URL will be cached. - * - * @access public - * @param int $seconds The autodiscovered feed URL cache duration. - */ - function set_autodiscovery_cache_duration($seconds = 604800) - { - $this->autodiscovery_cache_duration = (int) $seconds; - } - - /** - * Set the file system location where the cached files should be stored. - * - * @access public - * @param string $location The file system location. - */ - function set_cache_location($location = './cache') - { - $this->cache_location = (string) $location; - } - - /** - * Determines whether feed items should be sorted into reverse chronological order. - * - * @access public - * @param bool $enable Sort as reverse chronological order. - */ - function enable_order_by_date($enable = true) - { - $this->order_by_date = (bool) $enable; - } - - /** - * Allows you to override the character encoding reported by the feed. - * - * @access public - * @param string $encoding Character encoding. - */ - function set_input_encoding($encoding = false) - { - if ($encoding) - { - $this->input_encoding = (string) $encoding; - } - else - { - $this->input_encoding = false; - } - } - - /** - * Set how much feed autodiscovery to do - * - * @access public - * @see SIMPLEPIE_LOCATOR_NONE - * @see SIMPLEPIE_LOCATOR_AUTODISCOVERY - * @see SIMPLEPIE_LOCATOR_LOCAL_EXTENSION - * @see SIMPLEPIE_LOCATOR_LOCAL_BODY - * @see SIMPLEPIE_LOCATOR_REMOTE_EXTENSION - * @see SIMPLEPIE_LOCATOR_REMOTE_BODY - * @see SIMPLEPIE_LOCATOR_ALL - * @param int $level Feed Autodiscovery Level (level can be a - * combination of the above constants, see bitwise OR operator) - */ - function set_autodiscovery_level($level = SIMPLEPIE_LOCATOR_ALL) - { - $this->autodiscovery = (int) $level; - } - - /** - * Allows you to change which class SimplePie uses for caching. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_cache_class($class = 'SimplePie_Cache') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Cache')) - { - $this->cache_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for auto-discovery. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_locator_class($class = 'SimplePie_Locator') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Locator')) - { - $this->locator_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for XML parsing. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_parser_class($class = 'SimplePie_Parser') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Parser')) - { - $this->parser_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for remote file fetching. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_file_class($class = 'SimplePie_File') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_File')) - { - $this->file_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for data sanitization. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_sanitize_class($class = 'SimplePie_Sanitize') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Sanitize')) - { - $this->sanitize = new $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for handling feed items. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_item_class($class = 'SimplePie_Item') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Item')) - { - $this->item_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for handling author data. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_author_class($class = 'SimplePie_Author') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Author')) - { - $this->author_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for handling category data. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_category_class($class = 'SimplePie_Category') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Category')) - { - $this->category_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for feed enclosures. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_enclosure_class($class = 'SimplePie_Enclosure') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Enclosure')) - { - $this->enclosure_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for <media:text> captions - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_caption_class($class = 'SimplePie_Caption') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Caption')) - { - $this->caption_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for <media:copyright> - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_copyright_class($class = 'SimplePie_Copyright') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Copyright')) - { - $this->copyright_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for <media:credit> - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_credit_class($class = 'SimplePie_Credit') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Credit')) - { - $this->credit_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for <media:rating> - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_rating_class($class = 'SimplePie_Rating') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Rating')) - { - $this->rating_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for <media:restriction> - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_restriction_class($class = 'SimplePie_Restriction') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Restriction')) - { - $this->restriction_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses for content-type sniffing. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_content_type_sniffer_class($class = 'SimplePie_Content_Type_Sniffer') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Content_Type_Sniffer')) - { - $this->content_type_sniffer_class = $class; - return true; - } - return false; - } - - /** - * Allows you to change which class SimplePie uses item sources. - * Useful when you are overloading or extending SimplePie's default classes. - * - * @access public - * @param string $class Name of custom class. - * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation - * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation - */ - function set_source_class($class = 'SimplePie_Source') - { - if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Source')) - { - $this->source_class = $class; - return true; - } - return false; - } - - /** - * Allows you to override the default user agent string. - * - * @access public - * @param string $ua New user agent string. - */ - function set_useragent($ua = SIMPLEPIE_USERAGENT) - { - $this->useragent = (string) $ua; - } - - /** - * Set callback function to create cache filename with - * - * @access public - * @param mixed $function Callback function - */ - function set_cache_name_function($function = 'md5') - { - if (is_callable($function)) - { - $this->cache_name_function = $function; - } - } - - /** - * Set javascript query string parameter - * - * @access public - * @param mixed $get Javascript query string parameter - */ - function set_javascript($get = 'js') - { - if ($get) - { - $this->javascript = (string) $get; - } - else - { - $this->javascript = false; - } - } - - /** - * Set options to make SP as fast as possible. Forgoes a - * substantial amount of data sanitization in favor of speed. - * - * @access public - * @param bool $set Whether to set them or not - */ - function set_stupidly_fast($set = false) - { - if ($set) - { - $this->enable_order_by_date(false); - $this->remove_div(false); - $this->strip_comments(false); - $this->strip_htmltags(false); - $this->strip_attributes(false); - $this->set_image_handler(false); - } - } - - /** - * Set maximum number of feeds to check with autodiscovery - * - * @access public - * @param int $max Maximum number of feeds to check - */ - function set_max_checked_feeds($max = 10) - { - $this->max_checked_feeds = (int) $max; - } - - function remove_div($enable = true) - { - $this->sanitize->remove_div($enable); - } - - function strip_htmltags($tags = '', $encode = null) - { - if ($tags === '') - { - $tags = $this->strip_htmltags; - } - $this->sanitize->strip_htmltags($tags); - if ($encode !== null) - { - $this->sanitize->encode_instead_of_strip($tags); - } - } - - function encode_instead_of_strip($enable = true) - { - $this->sanitize->encode_instead_of_strip($enable); - } - - function strip_attributes($attribs = '') - { - if ($attribs === '') - { - $attribs = $this->strip_attributes; - } - $this->sanitize->strip_attributes($attribs); - } - - function set_output_encoding($encoding = 'UTF-8') - { - $this->sanitize->set_output_encoding($encoding); - } - - function strip_comments($strip = false) - { - $this->sanitize->strip_comments($strip); - } - - /** - * Set element/attribute key/value pairs of HTML attributes - * containing URLs that need to be resolved relative to the feed - * - * @access public - * @since 1.0 - * @param array $element_attribute Element/attribute key/value pairs - */ - function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite')) - { - $this->sanitize->set_url_replacements($element_attribute); - } - - /** - * Set the handler to enable the display of cached favicons. - * - * @access public - * @param str $page Web-accessible path to the handler_favicon.php file. - * @param str $qs The query string that the value should be passed to. - */ - function set_favicon_handler($page = false, $qs = 'i') - { - if ($page !== false) - { - $this->favicon_handler = $page . '?' . $qs . '='; - } - else - { - $this->favicon_handler = ''; - } - } - - /** - * Set the handler to enable the display of cached images. - * - * @access public - * @param str $page Web-accessible path to the handler_image.php file. - * @param str $qs The query string that the value should be passed to. - */ - function set_image_handler($page = false, $qs = 'i') - { - if ($page !== false) - { - $this->sanitize->set_image_handler($page . '?' . $qs . '='); - } - else - { - $this->image_handler = ''; - } - } - - /** - * Set the limit for items returned per-feed with multifeeds. - * - * @access public - * @param integer $limit The maximum number of items to return. - */ - function set_item_limit($limit = 0) - { - $this->item_limit = (int) $limit; - } - - function init() - { - // Check absolute bare minimum requirements. - if ((function_exists('version_compare') && version_compare(PHP_VERSION, '4.3.0', '<')) || !extension_loaded('xml') || !extension_loaded('pcre')) - { - return false; - } - // Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader. - elseif (!extension_loaded('xmlreader')) - { - static $xml_is_sane = null; - if ($xml_is_sane === null) - { - $parser_check = xml_parser_create(); - xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); - xml_parser_free($parser_check); - $xml_is_sane = isset($values[0]['value']); - } - if (!$xml_is_sane) - { - return false; - } - } - - if (isset($_GET[$this->javascript])) - { - SimplePie_Misc::output_javascript(); - exit; - } - - // Pass whatever was set with config options over to the sanitizer. - $this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->cache_class); - $this->sanitize->pass_file_data($this->file_class, $this->timeout, $this->useragent, $this->force_fsockopen); - - if ($this->feed_url !== null || $this->raw_data !== null) - { - $this->data = array(); - $this->multifeed_objects = array(); - $cache = false; - - if ($this->feed_url !== null) - { - $parsed_feed_url = SimplePie_Misc::parse_url($this->feed_url); - // Decide whether to enable caching - if ($this->cache && $parsed_feed_url['scheme'] !== '') - { - $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc'); - } - // If it's enabled and we don't want an XML dump, use the cache - if ($cache && !$this->xml_dump) - { - // Load the Cache - $this->data = $cache->load(); - if (!empty($this->data)) - { - // If the cache is for an outdated build of SimplePie - if (!isset($this->data['build']) || $this->data['build'] !== SIMPLEPIE_BUILD) - { - $cache->unlink(); - $this->data = array(); - } - // If we've hit a collision just rerun it with caching disabled - elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url) - { - $cache = false; - $this->data = array(); - } - // If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL. - elseif (isset($this->data['feed_url'])) - { - // If the autodiscovery cache is still valid use it. - if ($cache->mtime() + $this->autodiscovery_cache_duration > time()) - { - // Do not need to do feed autodiscovery yet. - if ($this->data['feed_url'] === $this->data['url']) - { - $cache->unlink(); - $this->data = array(); - } - else - { - $this->set_feed_url($this->data['feed_url']); - return $this->init(); - } - } - } - // Check if the cache has been updated - elseif ($cache->mtime() + $this->cache_duration < time()) - { - // If we have last-modified and/or etag set - if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) - { - $headers = array(); - if (isset($this->data['headers']['last-modified'])) - { - $headers['if-modified-since'] = $this->data['headers']['last-modified']; - } - if (isset($this->data['headers']['etag'])) - { - $headers['if-none-match'] = '"' . $this->data['headers']['etag'] . '"'; - } - $file = new $this->file_class($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen); - if ($file->success) - { - if ($file->status_code === 304) - { - $cache->touch(); - return true; - } - else - { - $headers = $file->headers; - } - } - else - { - unset($file); - } - } - } - // If the cache is still valid, just return true - else - { - return true; - } - } - // If the cache is empty, delete it - else - { - $cache->unlink(); - $this->data = array(); - } - } - // If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it. - if (!isset($file)) - { - if (is_a($this->file, 'SimplePie_File') && $this->file->url === $this->feed_url) - { - $file =& $this->file; - } - else - { - $file = new $this->file_class($this->feed_url, $this->timeout, 5, null, $this->useragent, $this->force_fsockopen); - } - } - // If the file connection has an error, set SimplePie::error to that and quit - if (!$file->success && !($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) - { - $this->error = $file->error; - if (!empty($this->data)) - { - return true; - } - else - { - return false; - } - } - - if (!$this->force_feed) - { - // Check if the supplied URL is a feed, if it isn't, look for it. - $locate = new $this->locator_class($file, $this->timeout, $this->useragent, $this->file_class, $this->max_checked_feeds, $this->content_type_sniffer_class); - if (!$locate->is_feed($file)) - { - // We need to unset this so that if SimplePie::set_file() has been called that object is untouched - unset($file); - if ($file = $locate->find($this->autodiscovery, $this->all_discovered_feeds)) - { - if ($cache) - { - $this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD); - if (!$cache->save($this)) - { - trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); - } - $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $file->url), 'spc'); - } - $this->feed_url = $file->url; - } - else - { - $this->error = "A feed could not be found at $this->feed_url. A feed with an invalid mime type may fall victim to this error, or " . SIMPLEPIE_NAME . " was unable to auto-discover it.. Use force_feed() if you are certain this URL is a real feed."; - SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__); - return false; - } - } - $locate = null; - } - - $headers = $file->headers; - $data = $file->body; - $sniffer = new $this->content_type_sniffer_class($file); - $sniffed = $sniffer->get_type(); - } - else - { - $data = $this->raw_data; - } - - // Set up array of possible encodings - $encodings = array(); - - // First check to see if input has been overridden. - if ($this->input_encoding !== false) - { - $encodings[] = $this->input_encoding; - } - - $application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity'); - $text_types = array('text/xml', 'text/xml-external-parsed-entity'); - - // RFC 3023 (only applies to sniffed content) - if (isset($sniffed)) - { - if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml') - { - if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) - { - $encodings[] = strtoupper($charset[1]); - } - $encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data)); - $encodings[] = 'UTF-8'; - } - elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') - { - if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) - { - $encodings[] = $charset[1]; - } - $encodings[] = 'US-ASCII'; - } - // Text MIME-type default - elseif (substr($sniffed, 0, 5) === 'text/') - { - $encodings[] = 'US-ASCII'; - } - } - - // Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1 - $encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data)); - $encodings[] = 'UTF-8'; - $encodings[] = 'ISO-8859-1'; - - // There's no point in trying an encoding twice - $encodings = array_unique($encodings); - - // If we want the XML, just output that with the most likely encoding and quit - if ($this->xml_dump) - { - header('Content-type: text/xml; charset=' . $encodings[0]); - echo $data; - exit; - } - - // Loop through each possible encoding, till we return something, or run out of possibilities - foreach ($encodings as $encoding) - { - // Change the encoding to UTF-8 (as we always use UTF-8 internally) - if ($utf8_data = SimplePie_Misc::change_encoding($data, $encoding, 'UTF-8')) - { - // Create new parser - $parser = new $this->parser_class(); - - // If it's parsed fine - if ($parser->parse($utf8_data, 'UTF-8')) - { - $this->data = $parser->get_data(); - if ($this->get_type() & ~SIMPLEPIE_TYPE_NONE) - { - if (isset($headers)) - { - $this->data['headers'] = $headers; - } - $this->data['build'] = SIMPLEPIE_BUILD; - - // Cache the file if caching is enabled - if ($cache && !$cache->save($this)) - { - trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); - } - return true; - } - else - { - $this->error = "A feed could not be found at $this->feed_url. This does not appear to be a valid RSS or Atom feed."; - SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__); - return false; - } - } - } - } - if (isset($parser)) - { - // We have an error, just set SimplePie_Misc::error to it and quit - $this->error = sprintf('This XML document is invalid, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column()); - } - else - { - $this->error = 'The data could not be converted to UTF-8. You MUST have either the iconv or mbstring extension installed. Upgrading to PHP 5.x (which includes iconv) is highly recommended.'; - } - SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__); - return false; - } - elseif (!empty($this->multifeed_url)) - { - $i = 0; - $success = 0; - $this->multifeed_objects = array(); - foreach ($this->multifeed_url as $url) - { - if (SIMPLEPIE_PHP5) - { - // This keyword needs to defy coding standards for PHP4 compatibility - $this->multifeed_objects[$i] = clone($this); - } - else - { - $this->multifeed_objects[$i] = $this; - } - $this->multifeed_objects[$i]->set_feed_url($url); - $success |= $this->multifeed_objects[$i]->init(); - $i++; - } - return (bool) $success; - } - else - { - return false; - } - } - - /** - * Return the error message for the occured error - * - * @access public - * @return string Error message - */ - function error() - { - return $this->error; - } - - function get_encoding() - { - return $this->sanitize->output_encoding; - } - - function handle_content_type($mime = 'text/html') - { - if (!headers_sent()) - { - $header = "Content-type: $mime;"; - if ($this->get_encoding()) - { - $header .= ' charset=' . $this->get_encoding(); - } - else - { - $header .= ' charset=UTF-8'; - } - header($header); - } - } - - function get_type() - { - if (!isset($this->data['type'])) - { - $this->data['type'] = SIMPLEPIE_TYPE_ALL; - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'])) - { - $this->data['type'] &= SIMPLEPIE_TYPE_ATOM_10; - } - elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'])) - { - $this->data['type'] &= SIMPLEPIE_TYPE_ATOM_03; - } - elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'])) - { - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['channel']) - || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['image']) - || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']) - || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['textinput'])) - { - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_10; - } - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['channel']) - || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['image']) - || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']) - || isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['textinput'])) - { - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_090; - } - } - elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'])) - { - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_ALL; - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) - { - switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version'])) - { - case '0.91': - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091; - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) - { - switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data'])) - { - case '0': - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_NETSCAPE; - break; - - case '24': - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_USERLAND; - break; - } - } - break; - - case '0.92': - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_092; - break; - - case '0.93': - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_093; - break; - - case '0.94': - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_094; - break; - - case '2.0': - $this->data['type'] &= SIMPLEPIE_TYPE_RSS_20; - break; - } - } - } - else - { - $this->data['type'] = SIMPLEPIE_TYPE_NONE; - } - } - return $this->data['type']; - } - - /** - * Returns the URL for the favicon of the feed's website. - * - * @todo Cache atom:icon - * @access public - * @since 1.0 - */ - function get_favicon() - { - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif (($url = $this->get_link()) !== null && preg_match('/^http(s)?:\/\//i', $url)) - { - $favicon = SimplePie_Misc::absolutize_url('/favicon.ico', $url); - - if ($this->cache && $this->favicon_handler) - { - $favicon_filename = call_user_func($this->cache_name_function, $favicon); - $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, $favicon_filename, 'spi'); - - if ($cache->load()) - { - return $this->sanitize($this->favicon_handler . $favicon_filename, SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - $file = new $this->file_class($favicon, $this->timeout / 10, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen); - - if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)) && strlen($file->body) > 0) - { - $sniffer = new $this->content_type_sniffer_class($file); - if (substr($sniffer->get_type(), 0, 6) === 'image/') - { - if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) - { - return $this->sanitize($this->favicon_handler . $favicon_filename, SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - trigger_error("$cache->name is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); - return $this->sanitize($favicon, SIMPLEPIE_CONSTRUCT_IRI); - } - } - // not an image - else - { - return false; - } - } - } - } - else - { - return $this->sanitize($favicon, SIMPLEPIE_CONSTRUCT_IRI); - } - } - return false; - } - - /** - * @todo If we have a perm redirect we should return the new URL - * @todo When we make the above change, let's support <itunes:new-feed-url> as well - * @todo Also, |atom:link|@rel=self - */ - function subscribe_url() - { - if ($this->feed_url !== null) - { - return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - return null; - } - } - - function subscribe_feed() - { - if ($this->feed_url !== null) - { - return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 2), SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - return null; - } - } - - function subscribe_outlook() - { - if ($this->feed_url !== null) - { - return $this->sanitize('outlook' . SimplePie_Misc::fix_protocol($this->feed_url, 2), SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - return null; - } - } - - function subscribe_podcast() - { - if ($this->feed_url !== null) - { - return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 3), SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - return null; - } - } - - function subscribe_itunes() - { - if ($this->feed_url !== null) - { - return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 4), SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - return null; - } - } - - /** - * Creates the subscribe_* methods' return data - * - * @access private - * @param string $feed_url String to prefix to the feed URL - * @param string $site_url String to prefix to the site URL (and - * suffix to the feed URL) - * @return mixed URL if feed exists, false otherwise - */ - function subscribe_service($feed_url, $site_url = null) - { - if ($this->subscribe_url()) - { - $return = $feed_url . rawurlencode($this->feed_url); - if ($site_url !== null && $this->get_link() !== null) - { - $return .= $site_url . rawurlencode($this->get_link()); - } - return $this->sanitize($return, SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - return null; - } - } - - function subscribe_aol() - { - return $this->subscribe_service('http://feeds.my.aol.com/add.jsp?url='); - } - - function subscribe_bloglines() - { - return $this->subscribe_service('http://www.bloglines.com/sub/'); - } - - function subscribe_eskobo() - { - return $this->subscribe_service('http://www.eskobo.com/?AddToMyPage='); - } - - function subscribe_feedfeeds() - { - return $this->subscribe_service('http://www.feedfeeds.com/add?feed='); - } - - function subscribe_feedster() - { - return $this->subscribe_service('http://www.feedster.com/myfeedster.php?action=addrss&confirm=no&rssurl='); - } - - function subscribe_google() - { - return $this->subscribe_service('http://fusion.google.com/add?feedurl='); - } - - function subscribe_gritwire() - { - return $this->subscribe_service('http://my.gritwire.com/feeds/addExternalFeed.aspx?FeedUrl='); - } - - function subscribe_msn() - { - return $this->subscribe_service('http://my.msn.com/addtomymsn.armx?id=rss&ut=', '&ru='); - } - - function subscribe_netvibes() - { - return $this->subscribe_service('http://www.netvibes.com/subscribe.php?url='); - } - - function subscribe_newsburst() - { - return $this->subscribe_service('http://www.newsburst.com/Source/?add='); - } - - function subscribe_newsgator() - { - return $this->subscribe_service('http://www.newsgator.com/ngs/subscriber/subext.aspx?url='); - } - - function subscribe_odeo() - { - return $this->subscribe_service('http://www.odeo.com/listen/subscribe?feed='); - } - - function subscribe_podnova() - { - return $this->subscribe_service('http://www.podnova.com/index_your_podcasts.srf?action=add&url='); - } - - function subscribe_rojo() - { - return $this->subscribe_service('http://www.rojo.com/add-subscription?resource='); - } - - function subscribe_yahoo() - { - return $this->subscribe_service('http://add.my.yahoo.com/rss?url='); - } - - function get_feed_tags($namespace, $tag) - { - $type = $this->get_type(); - if ($type & SIMPLEPIE_TYPE_ATOM_10) - { - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag])) - { - return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]; - } - } - if ($type & SIMPLEPIE_TYPE_ATOM_03) - { - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag])) - { - return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]; - } - } - if ($type & SIMPLEPIE_TYPE_RSS_RDF) - { - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag])) - { - return $this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]; - } - } - if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) - { - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag])) - { - return $this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]; - } - } - return null; - } - - function get_channel_tags($namespace, $tag) - { - $type = $this->get_type(); - if ($type & SIMPLEPIE_TYPE_ATOM_ALL) - { - if ($return = $this->get_feed_tags($namespace, $tag)) - { - return $return; - } - } - if ($type & SIMPLEPIE_TYPE_RSS_10) - { - if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'channel')) - { - if (isset($channel[0]['child'][$namespace][$tag])) - { - return $channel[0]['child'][$namespace][$tag]; - } - } - } - if ($type & SIMPLEPIE_TYPE_RSS_090) - { - if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'channel')) - { - if (isset($channel[0]['child'][$namespace][$tag])) - { - return $channel[0]['child'][$namespace][$tag]; - } - } - } - if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) - { - if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'channel')) - { - if (isset($channel[0]['child'][$namespace][$tag])) - { - return $channel[0]['child'][$namespace][$tag]; - } - } - } - return null; - } - - function get_image_tags($namespace, $tag) - { - $type = $this->get_type(); - if ($type & SIMPLEPIE_TYPE_RSS_10) - { - if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'image')) - { - if (isset($image[0]['child'][$namespace][$tag])) - { - return $image[0]['child'][$namespace][$tag]; - } - } - } - if ($type & SIMPLEPIE_TYPE_RSS_090) - { - if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'image')) - { - if (isset($image[0]['child'][$namespace][$tag])) - { - return $image[0]['child'][$namespace][$tag]; - } - } - } - if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION) - { - if ($image = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'image')) - { - if (isset($image[0]['child'][$namespace][$tag])) - { - return $image[0]['child'][$namespace][$tag]; - } - } - } - return null; - } - - function get_base($element = array()) - { - if (!($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION) && !empty($element['xml_base_explicit']) && isset($element['xml_base'])) - { - return $element['xml_base']; - } - elseif ($this->get_link() !== null) - { - return $this->get_link(); - } - else - { - return $this->subscribe_url(); - } - } - - function sanitize($data, $type, $base = '') - { - return $this->sanitize->sanitize($data, $type, $base); - } - - function get_title() - { - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_category($key = 0) - { - $categories = $this->get_categories(); - if (isset($categories[$key])) - { - return $categories[$key]; - } - else - { - return null; - } - } - - function get_categories() - { - $categories = array(); - - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['attribs']['']['term'])) - { - $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories[] = new $this->category_class($term, $scheme, $label); - } - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) - { - // This is really the label, but keep this as the term also for BC. - // Label will also work on retrieving because that falls back to term. - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - if (isset($category['attribs']['']['domain'])) - { - $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = null; - } - $categories[] = new $this->category_class($term, $scheme, null); - } - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) - { - $categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) - { - $categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - - if (!empty($categories)) - { - return SimplePie_Misc::array_unique($categories); - } - else - { - return null; - } - } - - function get_author($key = 0) - { - $authors = $this->get_authors(); - if (isset($authors[$key])) - { - return $authors[$key]; - } - else - { - return null; - } - } - - function get_authors() - { - $authors = array(); - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) - { - $name = null; - $uri = null; - $email = null; - $avatar = null; - $name_date = null; - $uri_date = null; - $avatar_date = null; - - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) - { - $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) - { - $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); - } - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) - { - $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0]['data'])) - { - $avatar = $this->sanitize($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0])); - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['name-updated'][0]['data'])) - { - $name_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['name-updated'][0]['data']; - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['uri-updated'][0]['data'])) - { - $uri_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['uri-updated'][0]['data']; - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar-updated'][0]['data'])) - { - $avatar_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar-updated'][0]['data']; - } - - if ($name !== null || $email !== null || $uri !== null || $avatar !== null || $name_date !== null || $uri_date !== null || $avatar_date !== null ) - { - $authors[] = new $this->author_class($name, $uri, $email, $avatar, $name_date, $uri_date, $avatar_date); - } - } - if ($author = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) - { - $name = null; - $url = null; - $email = null; - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) - { - $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) - { - $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); - } - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) - { - $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $url !== null) - { - $authors[] = new $this->author_class($name, $url, $email); - } - } - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) - { - $authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) - { - $authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) - { - $authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - - if (!empty($authors)) - { - return SimplePie_Misc::array_unique($authors); - } - else - { - return null; - } - } - - function get_contributor($key = 0) - { - $contributors = $this->get_contributors(); - if (isset($contributors[$key])) - { - return $contributors[$key]; - } - else - { - return null; - } - } - - function get_contributors() - { - $contributors = array(); - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) - { - $name = null; - $uri = null; - $email = null; - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) - { - $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) - { - $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) - { - $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $uri !== null) - { - $contributors[] = new $this->author_class($name, $uri, $email); - } - } - foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) - { - $name = null; - $url = null; - $email = null; - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) - { - $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) - { - $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) - { - $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $url !== null) - { - $contributors[] = new $this->author_class($name, $url, $email); - } - } - - if (!empty($contributors)) - { - return SimplePie_Misc::array_unique($contributors); - } - else - { - return null; - } - } - - function get_link($key = 0, $rel = 'alternate') - { - $links = $this->get_links($rel); - if (isset($links[$key])) - { - return $links[$key]; - } - else - { - return null; - } - } - - /** - * Added for parity between the parent-level and the item/entry-level. - */ - function get_permalink() - { - return $this->get_link(0); - } - - function get_links($rel = 'alternate') - { - if (!isset($this->data['links'])) - { - $this->data['links'] = array(); - if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link')) - { - foreach ($links as $link) - { - if (isset($link['attribs']['']['href'])) - { - $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; - $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - } - } - } - if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link')) - { - foreach ($links as $link) - { - if (isset($link['attribs']['']['href'])) - { - $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; - $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - - } - } - } - if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - - $keys = array_keys($this->data['links']); - foreach ($keys as $key) - { - if (SimplePie_Misc::is_isegment_nz_nc($key)) - { - if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) - { - $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); - $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; - } - else - { - $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; - } - } - elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) - { - $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; - } - $this->data['links'][$key] = array_unique($this->data['links'][$key]); - } - } - - if (isset($this->data['links'][$rel])) - { - return $this->data['links'][$rel]; - } - else - { - return null; - } - } - - function get_all_discovered_feeds() - { - return $this->all_discovered_feeds; - } - - function get_description() - { - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - else - { - return null; - } - } - - function get_copyright() - { - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_language() - { - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'])) - { - return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'])) - { - return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'])) - { - return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (isset($this->data['headers']['content-language'])) - { - return $this->sanitize($this->data['headers']['content-language'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_latitude() - { - - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) - { - return (float) $return[0]['data']; - } - elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) - { - return (float) $match[1]; - } - else - { - return null; - } - } - - function get_longitude() - { - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) - { - return (float) $return[0]['data']; - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) - { - return (float) $return[0]['data']; - } - elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) - { - return (float) $match[2]; - } - else - { - return null; - } - } - - function get_image_title() - { - if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_image_url() - { - if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image')) - { - return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'url')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'url')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - else - { - return null; - } - } - - function get_image_link() - { - if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - else - { - return null; - } - } - - function get_image_width() - { - if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'width')) - { - return round($return[0]['data']); - } - elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) - { - return 88.0; - } - else - { - return null; - } - } - - function get_image_height() - { - if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'height')) - { - return round($return[0]['data']); - } - elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url')) - { - return 31.0; - } - else - { - return null; - } - } - - function get_item_quantity($max = 0) - { - $max = (int) $max; - $qty = count($this->get_items()); - if ($max === 0) - { - return $qty; - } - else - { - return ($qty > $max) ? $max : $qty; - } - } - - function get_item($key = 0) - { - $items = $this->get_items(); - if (isset($items[$key])) - { - return $items[$key]; - } - else - { - return null; - } - } - - function get_items($start = 0, $end = 0) - { - if (!isset($this->data['items'])) - { - if (!empty($this->multifeed_objects)) - { - $this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit); - } - else - { - $this->data['items'] = array(); - if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'entry')) - { - $keys = array_keys($items); - foreach ($keys as $key) - { - $this->data['items'][] = new $this->item_class($this, $items[$key]); - } - } - if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'entry')) - { - $keys = array_keys($items); - foreach ($keys as $key) - { - $this->data['items'][] = new $this->item_class($this, $items[$key]); - } - } - if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'item')) - { - $keys = array_keys($items); - foreach ($keys as $key) - { - $this->data['items'][] = new $this->item_class($this, $items[$key]); - } - } - if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'item')) - { - $keys = array_keys($items); - foreach ($keys as $key) - { - $this->data['items'][] = new $this->item_class($this, $items[$key]); - } - } - if ($items = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'item')) - { - $keys = array_keys($items); - foreach ($keys as $key) - { - $this->data['items'][] = new $this->item_class($this, $items[$key]); - } - } - } - } - - if (!empty($this->data['items'])) - { - // If we want to order it by date, check if all items have a date, and then sort it - if ($this->order_by_date && empty($this->multifeed_objects)) - { - if (!isset($this->data['ordered_items'])) - { - $do_sort = true; - foreach ($this->data['items'] as $item) - { - if (!$item->get_date('U')) - { - $do_sort = false; - break; - } - } - $item = null; - $this->data['ordered_items'] = $this->data['items']; - if ($do_sort) - { - usort($this->data['ordered_items'], array(&$this, 'sort_items')); - } - } - $items = $this->data['ordered_items']; - } - else - { - $items = $this->data['items']; - } - - // Slice the data as desired - if ($end === 0) - { - return array_slice($items, $start); - } - else - { - return array_slice($items, $start, $end); - } - } - else - { - return array(); - } - } - - /** - * @static - */ - function sort_items($a, $b) - { - return $a->get_date('U') <= $b->get_date('U'); - } - - /** - * @static - */ - function merge_items($urls, $start = 0, $end = 0, $limit = 0) - { - if (is_array($urls) && sizeof($urls) > 0) - { - $items = array(); - foreach ($urls as $arg) - { - if (is_a($arg, 'SimplePie')) - { - $items = array_merge($items, $arg->get_items(0, $limit)); - } - else - { - trigger_error('Arguments must be SimplePie objects', E_USER_WARNING); - } - } - - $do_sort = true; - foreach ($items as $item) - { - if (!$item->get_date('U')) - { - $do_sort = false; - break; - } - } - $item = null; - if ($do_sort) - { - usort($items, array('SimplePie', 'sort_items')); - } - - if ($end === 0) - { - return array_slice($items, $start); - } - else - { - return array_slice($items, $start, $end); - } - } - else - { - trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING); - return array(); - } - } -} - -class SimplePie_Item -{ - var $feed; - var $data = array(); - - function SimplePie_Item($feed, $data) - { - $this->feed = $feed; - $this->data = $data; - } - - function __toString() - { - return md5(serialize($this->data)); - } - - /** - * Remove items that link back to this before destroying this object - */ - function __destruct() - { - if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode')) - { - unset($this->feed); - } - } - - function get_item_tags($namespace, $tag) - { - if (isset($this->data['child'][$namespace][$tag])) - { - return $this->data['child'][$namespace][$tag]; - } - else - { - return null; - } - } - - function get_base($element = array()) - { - return $this->feed->get_base($element); - } - - function sanitize($data, $type, $base = '') - { - return $this->feed->sanitize($data, $type, $base); - } - - function get_feed() - { - return $this->feed; - } - - function get_id($hash = false) - { - if (!$hash) - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'identifier')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'identifier')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (($return = $this->get_permalink()) !== null) - { - return $return; - } - elseif (($return = $this->get_title()) !== null) - { - return $return; - } - } - if ($this->get_permalink() !== null || $this->get_title() !== null) - { - return md5($this->get_permalink() . $this->get_title()); - } - else - { - return md5(serialize($this->data)); - } - } - - function get_title() - { - if (!isset($this->data['title'])) - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) - { - $this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) - { - $this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) - { - $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) - { - $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) - { - $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) - { - $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) - { - $this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $this->data['title'] = null; - } - } - return $this->data['title']; - } - - function get_description($description_only = false) - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'summary')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'summary')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (!$description_only) - { - return $this->get_content(true); - } - else - { - return null; - } - } - - function get_content($content_only = false) - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'content')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_content_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'content')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - elseif (!$content_only) - { - return $this->get_description(true); - } - else - { - return null; - } - } - - function get_category($key = 0) - { - $categories = $this->get_categories(); - if (isset($categories[$key])) - { - return $categories[$key]; - } - else - { - return null; - } - } - - function get_categories() - { - $categories = array(); - - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['attribs']['']['term'])) - { - $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories[] = new $this->feed->category_class($term, $scheme, $label); - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) - { - // This is really the label, but keep this as the term also for BC. - // Label will also work on retrieving because that falls back to term. - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - if (isset($category['attribs']['']['domain'])) - { - $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = null; - } - $categories[] = new $this->feed->category_class($term, $scheme, null); - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) - { - $categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) - { - $categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - - if (!empty($categories)) - { - return SimplePie_Misc::array_unique($categories); - } - else - { - return null; - } - } - - function get_author($key = 0) - { - $authors = $this->get_authors(); - if (isset($authors[$key])) - { - return $authors[$key]; - } - else - { - return null; - } - } - - function get_contributor($key = 0) - { - $contributors = $this->get_contributors(); - if (isset($contributors[$key])) - { - return $contributors[$key]; - } - else - { - return null; - } - } - - function get_contributors() - { - $contributors = array(); - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) - { - $name = null; - $uri = null; - $email = null; - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) - { - $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) - { - $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) - { - $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $uri !== null) - { - $contributors[] = new $this->feed->author_class($name, $uri, $email); - } - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) - { - $name = null; - $url = null; - $email = null; - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) - { - $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) - { - $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) - { - $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $url !== null) - { - $contributors[] = new $this->feed->author_class($name, $url, $email); - } - } - - if (!empty($contributors)) - { - return SimplePie_Misc::array_unique($contributors); - } - else - { - return null; - } - } - - function get_authors() - { - $authors = array(); - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) - { - $name = null; - $uri = null; - $email = null; - $avatar = null; - $name_date = null; - $uri_date = null; - $avatar_date = null; - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) - { - $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) - { - $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); - } - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) - { - $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0]['data'])) - { - $avatar = $this->sanitize($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0])); - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['name-updated'][0]['data'])) - { - $name_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['name-updated'][0]['data']; - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['uri-updated'][0]['data'])) - { - $uri_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['uri-updated'][0]['data']; - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar-updated'][0]['data'])) - { - $avatar_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar-updated'][0]['data']; - } - - if ($name !== null || $email !== null || $uri !== null || $avatar !== null || $name_date !== null || $uri_date !== null || $avatar_date !== null ) - { - $authors[] = new $this->feed->author_class($name, $uri, $email, $avatar, $name_date, $uri_date, $avatar_date); - } - } - if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) - { - $name = null; - $url = null; - $email = null; - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) - { - $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) - { - $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); - } - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) - { - $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $url !== null) - { - $authors[] = new $this->feed->author_class($name, $url, $email); - } - } - if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'author')) - { - $authors[] = new $this->feed->author_class(null, null, $this->sanitize($author[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) - { - $authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) - { - $authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) - { - $authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - - if (!empty($authors)) - { - return SimplePie_Misc::array_unique($authors); - } - elseif (($source = $this->get_source()) && ($authors = $source->get_authors())) - { - return $authors; - } - elseif ($authors = $this->feed->get_authors()) - { - return $authors; - } - else - { - return null; - } - } - - function get_copyright() - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_date($date_format = 'j F Y, g:i a') - { - if (!isset($this->data['date'])) - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date')) - { - $this->data['date']['raw'] = $return[0]['data']; - } - - if (!empty($this->data['date']['raw'])) - { - $parser = SimplePie_Parse_Date::get(); - $this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']); - } - else - { - $this->data['date'] = null; - } - } - if ($this->data['date']) - { - $date_format = (string) $date_format; - switch ($date_format) - { - case '': - return $this->sanitize($this->data['date']['raw'], SIMPLEPIE_CONSTRUCT_TEXT); - - case 'U': - return $this->data['date']['parsed']; - - default: - return date($date_format, $this->data['date']['parsed']); - } - } - else - { - return null; - } - } - - function get_local_date($date_format = '%c') - { - if (!$date_format) - { - return $this->sanitize($this->get_date(''), SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (($date = $this->get_date('U')) !== null) - { - return strftime($date_format, $date); - } - else - { - return null; - } - } - - function get_permalink() - { - $link = $this->get_link(); - $enclosure = $this->get_enclosure(0); - if ($link !== null) - { - return $link; - } - elseif ($enclosure !== null) - { - return $enclosure->get_link(); - } - else - { - return null; - } - } - - function get_link($key = 0, $rel = 'alternate') - { - $links = $this->get_links($rel); - if ($links[$key] !== null) - { - return $links[$key]; - } - else - { - return null; - } - } - - function get_links($rel = 'alternate') - { - if (!isset($this->data['links'])) - { - $this->data['links'] = array(); - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) - { - if (isset($link['attribs']['']['href'])) - { - $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; - $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - - } - } - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) - { - if (isset($link['attribs']['']['href'])) - { - $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; - $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - } - } - if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) - { - if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true') - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - } - - $keys = array_keys($this->data['links']); - foreach ($keys as $key) - { - if (SimplePie_Misc::is_isegment_nz_nc($key)) - { - if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) - { - $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); - $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; - } - else - { - $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; - } - } - elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) - { - $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; - } - $this->data['links'][$key] = array_unique($this->data['links'][$key]); - } - } - if (isset($this->data['links'][$rel])) - { - return $this->data['links'][$rel]; - } - else - { - return null; - } - } - - /** - * @todo Add ability to prefer one type of content over another (in a media group). - */ - function get_enclosure($key = 0, $prefer = null) - { - $enclosures = $this->get_enclosures(); - if (isset($enclosures[$key])) - { - return $enclosures[$key]; - } - else - { - return null; - } - } - - /** - * Grabs all available enclosures (podcasts, etc.) - * - * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS. - * - * At this point, we're pretty much assuming that all enclosures for an item are the same content. Anything else is too complicated to properly support. - * - * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). - * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists). - */ - function get_enclosures() - { - if (!isset($this->data['enclosures'])) - { - $this->data['enclosures'] = array(); - - // Elements - $captions_parent = null; - $categories_parent = null; - $copyrights_parent = null; - $credits_parent = null; - $description_parent = null; - $duration_parent = null; - $hashes_parent = null; - $keywords_parent = null; - $player_parent = null; - $ratings_parent = null; - $restrictions_parent = null; - $thumbnails_parent = null; - $title_parent = null; - - // Let's do the channel and item-level ones first, and just re-use them if we need to. - $parent = $this->get_feed(); - - // CAPTIONS - if ($captions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) - { - foreach ($captions as $caption) - { - $caption_type = null; - $caption_lang = null; - $caption_startTime = null; - $caption_endTime = null; - $caption_text = null; - if (isset($caption['attribs']['']['type'])) - { - $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['lang'])) - { - $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['start'])) - { - $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['end'])) - { - $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['data'])) - { - $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); - } - } - elseif ($captions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text')) - { - foreach ($captions as $caption) - { - $caption_type = null; - $caption_lang = null; - $caption_startTime = null; - $caption_endTime = null; - $caption_text = null; - if (isset($caption['attribs']['']['type'])) - { - $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['lang'])) - { - $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['start'])) - { - $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['end'])) - { - $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['data'])) - { - $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); - } - } - if (is_array($captions_parent)) - { - $captions_parent = array_values(SimplePie_Misc::array_unique($captions_parent)); - } - - // CATEGORIES - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['data'])) - { - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = 'http://search.yahoo.com/mrss/category_schema'; - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); - } - foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['data'])) - { - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = 'http://search.yahoo.com/mrss/category_schema'; - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); - } - foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'category') as $category) - { - $term = null; - $scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; - $label = null; - if (isset($category['attribs']['']['text'])) - { - $label = $this->sanitize($category['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); - - if (isset($category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'])) - { - foreach ((array) $category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'] as $subcategory) - { - if (isset($subcategory['attribs']['']['text'])) - { - $label = $this->sanitize($subcategory['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories_parent[] = new $this->feed->category_class($term, $scheme, $label); - } - } - } - if (is_array($categories_parent)) - { - $categories_parent = array_values(SimplePie_Misc::array_unique($categories_parent)); - } - - // COPYRIGHT - if ($copyright = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) - { - $copyright_url = null; - $copyright_label = null; - if (isset($copyright[0]['attribs']['']['url'])) - { - $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($copyright[0]['data'])) - { - $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label); - } - elseif ($copyright = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright')) - { - $copyright_url = null; - $copyright_label = null; - if (isset($copyright[0]['attribs']['']['url'])) - { - $copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($copyright[0]['data'])) - { - $copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label); - } - - // CREDITS - if ($credits = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) - { - foreach ($credits as $credit) - { - $credit_role = null; - $credit_scheme = null; - $credit_name = null; - if (isset($credit['attribs']['']['role'])) - { - $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($credit['attribs']['']['scheme'])) - { - $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $credit_scheme = 'urn:ebu'; - } - if (isset($credit['data'])) - { - $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); - } - } - elseif ($credits = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit')) - { - foreach ($credits as $credit) - { - $credit_role = null; - $credit_scheme = null; - $credit_name = null; - if (isset($credit['attribs']['']['role'])) - { - $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($credit['attribs']['']['scheme'])) - { - $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $credit_scheme = 'urn:ebu'; - } - if (isset($credit['data'])) - { - $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); - } - } - if (is_array($credits_parent)) - { - $credits_parent = array_values(SimplePie_Misc::array_unique($credits_parent)); - } - - // DESCRIPTION - if ($description_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) - { - if (isset($description_parent[0]['data'])) - { - $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - } - elseif ($description_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description')) - { - if (isset($description_parent[0]['data'])) - { - $description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - } - - // DURATION - if ($duration_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'duration')) - { - $seconds = null; - $minutes = null; - $hours = null; - if (isset($duration_parent[0]['data'])) - { - $temp = explode(':', $this->sanitize($duration_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - if (sizeof($temp) > 0) - { - (int) $seconds = array_pop($temp); - } - if (sizeof($temp) > 0) - { - (int) $minutes = array_pop($temp); - $seconds += $minutes * 60; - } - if (sizeof($temp) > 0) - { - (int) $hours = array_pop($temp); - $seconds += $hours * 3600; - } - unset($temp); - $duration_parent = $seconds; - } - } - - // HASHES - if ($hashes_iterator = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) - { - foreach ($hashes_iterator as $hash) - { - $value = null; - $algo = null; - if (isset($hash['data'])) - { - $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($hash['attribs']['']['algo'])) - { - $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $algo = 'md5'; - } - $hashes_parent[] = $algo.':'.$value; - } - } - elseif ($hashes_iterator = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash')) - { - foreach ($hashes_iterator as $hash) - { - $value = null; - $algo = null; - if (isset($hash['data'])) - { - $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($hash['attribs']['']['algo'])) - { - $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $algo = 'md5'; - } - $hashes_parent[] = $algo.':'.$value; - } - } - if (is_array($hashes_parent)) - { - $hashes_parent = array_values(SimplePie_Misc::array_unique($hashes_parent)); - } - - // KEYWORDS - if ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) - { - if (isset($keywords[0]['data'])) - { - $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - foreach ($temp as $word) - { - $keywords_parent[] = trim($word); - } - } - unset($temp); - } - elseif ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) - { - if (isset($keywords[0]['data'])) - { - $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - foreach ($temp as $word) - { - $keywords_parent[] = trim($word); - } - } - unset($temp); - } - elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords')) - { - if (isset($keywords[0]['data'])) - { - $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - foreach ($temp as $word) - { - $keywords_parent[] = trim($word); - } - } - unset($temp); - } - elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords')) - { - if (isset($keywords[0]['data'])) - { - $temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - foreach ($temp as $word) - { - $keywords_parent[] = trim($word); - } - } - unset($temp); - } - if (is_array($keywords_parent)) - { - $keywords_parent = array_values(SimplePie_Misc::array_unique($keywords_parent)); - } - - // PLAYER - if ($player_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) - { - if (isset($player_parent[0]['attribs']['']['url'])) - { - $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - } - elseif ($player_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player')) - { - if (isset($player_parent[0]['attribs']['']['url'])) - { - $player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - } - - // RATINGS - if ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) - { - foreach ($ratings as $rating) - { - $rating_scheme = null; - $rating_value = null; - if (isset($rating['attribs']['']['scheme'])) - { - $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $rating_scheme = 'urn:simple'; - } - if (isset($rating['data'])) - { - $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); - } - } - elseif ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) - { - foreach ($ratings as $rating) - { - $rating_scheme = 'urn:itunes'; - $rating_value = null; - if (isset($rating['data'])) - { - $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); - } - } - elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating')) - { - foreach ($ratings as $rating) - { - $rating_scheme = null; - $rating_value = null; - if (isset($rating['attribs']['']['scheme'])) - { - $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $rating_scheme = 'urn:simple'; - } - if (isset($rating['data'])) - { - $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); - } - } - elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit')) - { - foreach ($ratings as $rating) - { - $rating_scheme = 'urn:itunes'; - $rating_value = null; - if (isset($rating['data'])) - { - $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value); - } - } - if (is_array($ratings_parent)) - { - $ratings_parent = array_values(SimplePie_Misc::array_unique($ratings_parent)); - } - - // RESTRICTIONS - if ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) - { - foreach ($restrictions as $restriction) - { - $restriction_relationship = null; - $restriction_type = null; - $restriction_value = null; - if (isset($restriction['attribs']['']['relationship'])) - { - $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['attribs']['']['type'])) - { - $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['data'])) - { - $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); - } - } - elseif ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) - { - foreach ($restrictions as $restriction) - { - $restriction_relationship = 'allow'; - $restriction_type = null; - $restriction_value = 'itunes'; - if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') - { - $restriction_relationship = 'deny'; - } - $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); - } - } - elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction')) - { - foreach ($restrictions as $restriction) - { - $restriction_relationship = null; - $restriction_type = null; - $restriction_value = null; - if (isset($restriction['attribs']['']['relationship'])) - { - $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['attribs']['']['type'])) - { - $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['data'])) - { - $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); - } - } - elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block')) - { - foreach ($restrictions as $restriction) - { - $restriction_relationship = 'allow'; - $restriction_type = null; - $restriction_value = 'itunes'; - if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes') - { - $restriction_relationship = 'deny'; - } - $restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); - } - } - if (is_array($restrictions_parent)) - { - $restrictions_parent = array_values(SimplePie_Misc::array_unique($restrictions_parent)); - } - - // THUMBNAILS - if ($thumbnails = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) - { - foreach ($thumbnails as $thumbnail) - { - if (isset($thumbnail['attribs']['']['url'])) - { - $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - } - } - elseif ($thumbnails = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail')) - { - foreach ($thumbnails as $thumbnail) - { - if (isset($thumbnail['attribs']['']['url'])) - { - $thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - } - } - - // TITLES - if ($title_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) - { - if (isset($title_parent[0]['data'])) - { - $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - } - elseif ($title_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title')) - { - if (isset($title_parent[0]['data'])) - { - $title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - } - - // Clear the memory - unset($parent); - - // Attributes - $bitrate = null; - $channels = null; - $duration = null; - $expression = null; - $framerate = null; - $height = null; - $javascript = null; - $lang = null; - $length = null; - $medium = null; - $samplingrate = null; - $type = null; - $url = null; - $width = null; - - // Elements - $captions = null; - $categories = null; - $copyrights = null; - $credits = null; - $description = null; - $hashes = null; - $keywords = null; - $player = null; - $ratings = null; - $restrictions = null; - $thumbnails = null; - $title = null; - - // If we have media:group tags, loop through them. - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group') as $group) - { - if(isset($group['child']) && isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) - { - // If we have media:content tags, loop through them. - foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) - { - if (isset($content['attribs']['']['url'])) - { - // Attributes - $bitrate = null; - $channels = null; - $duration = null; - $expression = null; - $framerate = null; - $height = null; - $javascript = null; - $lang = null; - $length = null; - $medium = null; - $samplingrate = null; - $type = null; - $url = null; - $width = null; - - // Elements - $captions = null; - $categories = null; - $copyrights = null; - $credits = null; - $description = null; - $hashes = null; - $keywords = null; - $player = null; - $ratings = null; - $restrictions = null; - $thumbnails = null; - $title = null; - - // Start checking the attributes of media:content - if (isset($content['attribs']['']['bitrate'])) - { - $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['channels'])) - { - $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['duration'])) - { - $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $duration = $duration_parent; - } - if (isset($content['attribs']['']['expression'])) - { - $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['framerate'])) - { - $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['height'])) - { - $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['lang'])) - { - $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['fileSize'])) - { - $length = ceil($content['attribs']['']['fileSize']); - } - if (isset($content['attribs']['']['medium'])) - { - $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['samplingrate'])) - { - $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['type'])) - { - $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['width'])) - { - $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - - // Checking the other optional media: elements. Priority: media:content, media:group, item, channel - - // CAPTIONS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) - { - $caption_type = null; - $caption_lang = null; - $caption_startTime = null; - $caption_endTime = null; - $caption_text = null; - if (isset($caption['attribs']['']['type'])) - { - $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['lang'])) - { - $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['start'])) - { - $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['end'])) - { - $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['data'])) - { - $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); - } - if (is_array($captions)) - { - $captions = array_values(SimplePie_Misc::array_unique($captions)); - } - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) - { - foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) - { - $caption_type = null; - $caption_lang = null; - $caption_startTime = null; - $caption_endTime = null; - $caption_text = null; - if (isset($caption['attribs']['']['type'])) - { - $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['lang'])) - { - $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['start'])) - { - $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['end'])) - { - $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['data'])) - { - $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); - } - if (is_array($captions)) - { - $captions = array_values(SimplePie_Misc::array_unique($captions)); - } - } - else - { - $captions = $captions_parent; - } - - // CATEGORIES - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) - { - foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['data'])) - { - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = 'http://search.yahoo.com/mrss/category_schema'; - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories[] = new $this->feed->category_class($term, $scheme, $label); - } - } - if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) - { - foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['data'])) - { - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = 'http://search.yahoo.com/mrss/category_schema'; - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories[] = new $this->feed->category_class($term, $scheme, $label); - } - } - if (is_array($categories) && is_array($categories_parent)) - { - $categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent))); - } - elseif (is_array($categories)) - { - $categories = array_values(SimplePie_Misc::array_unique($categories)); - } - elseif (is_array($categories_parent)) - { - $categories = array_values(SimplePie_Misc::array_unique($categories_parent)); - } - - // COPYRIGHTS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) - { - $copyright_url = null; - $copyright_label = null; - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) - { - $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) - { - $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label); - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) - { - $copyright_url = null; - $copyright_label = null; - if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) - { - $copyright_url = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) - { - $copyright_label = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label); - } - else - { - $copyrights = $copyrights_parent; - } - - // CREDITS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) - { - $credit_role = null; - $credit_scheme = null; - $credit_name = null; - if (isset($credit['attribs']['']['role'])) - { - $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($credit['attribs']['']['scheme'])) - { - $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $credit_scheme = 'urn:ebu'; - } - if (isset($credit['data'])) - { - $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); - } - if (is_array($credits)) - { - $credits = array_values(SimplePie_Misc::array_unique($credits)); - } - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) - { - foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) - { - $credit_role = null; - $credit_scheme = null; - $credit_name = null; - if (isset($credit['attribs']['']['role'])) - { - $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($credit['attribs']['']['scheme'])) - { - $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $credit_scheme = 'urn:ebu'; - } - if (isset($credit['data'])) - { - $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); - } - if (is_array($credits)) - { - $credits = array_values(SimplePie_Misc::array_unique($credits)); - } - } - else - { - $credits = $credits_parent; - } - - // DESCRIPTION - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) - { - $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) - { - $description = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $description = $description_parent; - } - - // HASHES - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) - { - $value = null; - $algo = null; - if (isset($hash['data'])) - { - $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($hash['attribs']['']['algo'])) - { - $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $algo = 'md5'; - } - $hashes[] = $algo.':'.$value; - } - if (is_array($hashes)) - { - $hashes = array_values(SimplePie_Misc::array_unique($hashes)); - } - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) - { - foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) - { - $value = null; - $algo = null; - if (isset($hash['data'])) - { - $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($hash['attribs']['']['algo'])) - { - $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $algo = 'md5'; - } - $hashes[] = $algo.':'.$value; - } - if (is_array($hashes)) - { - $hashes = array_values(SimplePie_Misc::array_unique($hashes)); - } - } - else - { - $hashes = $hashes_parent; - } - - // KEYWORDS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) - { - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) - { - $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - foreach ($temp as $word) - { - $keywords[] = trim($word); - } - unset($temp); - } - if (is_array($keywords)) - { - $keywords = array_values(SimplePie_Misc::array_unique($keywords)); - } - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) - { - if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) - { - $temp = explode(',', $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - foreach ($temp as $word) - { - $keywords[] = trim($word); - } - unset($temp); - } - if (is_array($keywords)) - { - $keywords = array_values(SimplePie_Misc::array_unique($keywords)); - } - } - else - { - $keywords = $keywords_parent; - } - - // PLAYER - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) - { - $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) - { - $player = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - $player = $player_parent; - } - - // RATINGS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) - { - $rating_scheme = null; - $rating_value = null; - if (isset($rating['attribs']['']['scheme'])) - { - $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $rating_scheme = 'urn:simple'; - } - if (isset($rating['data'])) - { - $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value); - } - if (is_array($ratings)) - { - $ratings = array_values(SimplePie_Misc::array_unique($ratings)); - } - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) - { - foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) - { - $rating_scheme = null; - $rating_value = null; - if (isset($rating['attribs']['']['scheme'])) - { - $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $rating_scheme = 'urn:simple'; - } - if (isset($rating['data'])) - { - $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value); - } - if (is_array($ratings)) - { - $ratings = array_values(SimplePie_Misc::array_unique($ratings)); - } - } - else - { - $ratings = $ratings_parent; - } - - // RESTRICTIONS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) - { - $restriction_relationship = null; - $restriction_type = null; - $restriction_value = null; - if (isset($restriction['attribs']['']['relationship'])) - { - $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['attribs']['']['type'])) - { - $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['data'])) - { - $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); - } - if (is_array($restrictions)) - { - $restrictions = array_values(SimplePie_Misc::array_unique($restrictions)); - } - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) - { - foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) - { - $restriction_relationship = null; - $restriction_type = null; - $restriction_value = null; - if (isset($restriction['attribs']['']['relationship'])) - { - $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['attribs']['']['type'])) - { - $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['data'])) - { - $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); - } - if (is_array($restrictions)) - { - $restrictions = array_values(SimplePie_Misc::array_unique($restrictions)); - } - } - else - { - $restrictions = $restrictions_parent; - } - - // THUMBNAILS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) - { - $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - if (is_array($thumbnails)) - { - $thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails)); - } - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) - { - foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) - { - $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - if (is_array($thumbnails)) - { - $thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails)); - } - } - else - { - $thumbnails = $thumbnails_parent; - } - - // TITLES - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) - { - $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) - { - $title = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $title = $title_parent; - } - - $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width); - } - } - } - } - - // If we have standalone media:content tags, loop through them. - if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'])) - { - foreach ((array) $this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content) - { - if (isset($content['attribs']['']['url'])) - { - // Attributes - $bitrate = null; - $channels = null; - $duration = null; - $expression = null; - $framerate = null; - $height = null; - $javascript = null; - $lang = null; - $length = null; - $medium = null; - $samplingrate = null; - $type = null; - $url = null; - $width = null; - - // Elements - $captions = null; - $categories = null; - $copyrights = null; - $credits = null; - $description = null; - $hashes = null; - $keywords = null; - $player = null; - $ratings = null; - $restrictions = null; - $thumbnails = null; - $title = null; - - // Start checking the attributes of media:content - if (isset($content['attribs']['']['bitrate'])) - { - $bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['channels'])) - { - $channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['duration'])) - { - $duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $duration = $duration_parent; - } - if (isset($content['attribs']['']['expression'])) - { - $expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['framerate'])) - { - $framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['height'])) - { - $height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['lang'])) - { - $lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['fileSize'])) - { - $length = ceil($content['attribs']['']['fileSize']); - } - if (isset($content['attribs']['']['medium'])) - { - $medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['samplingrate'])) - { - $samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['type'])) - { - $type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['attribs']['']['width'])) - { - $width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - - // Checking the other optional media: elements. Priority: media:content, media:group, item, channel - - // CAPTIONS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption) - { - $caption_type = null; - $caption_lang = null; - $caption_startTime = null; - $caption_endTime = null; - $caption_text = null; - if (isset($caption['attribs']['']['type'])) - { - $caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['lang'])) - { - $caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['start'])) - { - $caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['attribs']['']['end'])) - { - $caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($caption['data'])) - { - $caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text); - } - if (is_array($captions)) - { - $captions = array_values(SimplePie_Misc::array_unique($captions)); - } - } - else - { - $captions = $captions_parent; - } - - // CATEGORIES - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'])) - { - foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['data'])) - { - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = 'http://search.yahoo.com/mrss/category_schema'; - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories[] = new $this->feed->category_class($term, $scheme, $label); - } - } - if (is_array($categories) && is_array($categories_parent)) - { - $categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent))); - } - elseif (is_array($categories)) - { - $categories = array_values(SimplePie_Misc::array_unique($categories)); - } - elseif (is_array($categories_parent)) - { - $categories = array_values(SimplePie_Misc::array_unique($categories_parent)); - } - else - { - $categories = null; - } - - // COPYRIGHTS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'])) - { - $copyright_url = null; - $copyright_label = null; - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'])) - { - $copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'])) - { - $copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label); - } - else - { - $copyrights = $copyrights_parent; - } - - // CREDITS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit) - { - $credit_role = null; - $credit_scheme = null; - $credit_name = null; - if (isset($credit['attribs']['']['role'])) - { - $credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($credit['attribs']['']['scheme'])) - { - $credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $credit_scheme = 'urn:ebu'; - } - if (isset($credit['data'])) - { - $credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name); - } - if (is_array($credits)) - { - $credits = array_values(SimplePie_Misc::array_unique($credits)); - } - } - else - { - $credits = $credits_parent; - } - - // DESCRIPTION - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'])) - { - $description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $description = $description_parent; - } - - // HASHES - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash) - { - $value = null; - $algo = null; - if (isset($hash['data'])) - { - $value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($hash['attribs']['']['algo'])) - { - $algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $algo = 'md5'; - } - $hashes[] = $algo.':'.$value; - } - if (is_array($hashes)) - { - $hashes = array_values(SimplePie_Misc::array_unique($hashes)); - } - } - else - { - $hashes = $hashes_parent; - } - - // KEYWORDS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'])) - { - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'])) - { - $temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)); - foreach ($temp as $word) - { - $keywords[] = trim($word); - } - unset($temp); - } - if (is_array($keywords)) - { - $keywords = array_values(SimplePie_Misc::array_unique($keywords)); - } - } - else - { - $keywords = $keywords_parent; - } - - // PLAYER - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'])) - { - $player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - $player = $player_parent; - } - - // RATINGS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating) - { - $rating_scheme = null; - $rating_value = null; - if (isset($rating['attribs']['']['scheme'])) - { - $rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $rating_scheme = 'urn:simple'; - } - if (isset($rating['data'])) - { - $rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value); - } - if (is_array($ratings)) - { - $ratings = array_values(SimplePie_Misc::array_unique($ratings)); - } - } - else - { - $ratings = $ratings_parent; - } - - // RESTRICTIONS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction) - { - $restriction_relationship = null; - $restriction_type = null; - $restriction_value = null; - if (isset($restriction['attribs']['']['relationship'])) - { - $restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['attribs']['']['type'])) - { - $restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($restriction['data'])) - { - $restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value); - } - if (is_array($restrictions)) - { - $restrictions = array_values(SimplePie_Misc::array_unique($restrictions)); - } - } - else - { - $restrictions = $restrictions_parent; - } - - // THUMBNAILS - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'])) - { - foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail) - { - $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI); - } - if (is_array($thumbnails)) - { - $thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails)); - } - } - else - { - $thumbnails = $thumbnails_parent; - } - - // TITLES - if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'])) - { - $title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $title = $title_parent; - } - - $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width); - } - } - } - - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link) - { - if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') - { - // Attributes - $bitrate = null; - $channels = null; - $duration = null; - $expression = null; - $framerate = null; - $height = null; - $javascript = null; - $lang = null; - $length = null; - $medium = null; - $samplingrate = null; - $type = null; - $url = null; - $width = null; - $title = $title_parent; - - $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - if (isset($link['attribs']['']['type'])) - { - $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($link['attribs']['']['length'])) - { - $length = ceil($link['attribs']['']['length']); - } - if (isset($link['attribs']['']['title'])) - { - $title = $this->sanitize($link['attribs']['']['title'], SIMPLEPIE_CONSTRUCT_TEXT); - } - - // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor - $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title, $width); - } - } - - foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link) - { - if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure') - { - // Attributes - $bitrate = null; - $channels = null; - $duration = null; - $expression = null; - $framerate = null; - $height = null; - $javascript = null; - $lang = null; - $length = null; - $medium = null; - $samplingrate = null; - $type = null; - $url = null; - $width = null; - - $url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - if (isset($link['attribs']['']['type'])) - { - $type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($link['attribs']['']['length'])) - { - $length = ceil($link['attribs']['']['length']); - } - - // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor - $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width); - } - } - - if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure')) - { - if (isset($enclosure[0]['attribs']['']['url'])) - { - // Attributes - $bitrate = null; - $channels = null; - $duration = null; - $expression = null; - $framerate = null; - $height = null; - $javascript = null; - $lang = null; - $length = null; - $medium = null; - $samplingrate = null; - $type = null; - $url = null; - $width = null; - - $url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0])); - if (isset($enclosure[0]['attribs']['']['type'])) - { - $type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($enclosure[0]['attribs']['']['length'])) - { - $length = ceil($enclosure[0]['attribs']['']['length']); - } - - // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor - $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width); - } - } - - if (sizeof($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $restrictions_parent || $samplingrate || $thumbnails_parent || $title_parent || $width)) - { - // Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor - $this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width); - } - - $this->data['enclosures'] = array_values(SimplePie_Misc::array_unique($this->data['enclosures'])); - } - if (!empty($this->data['enclosures'])) - { - return $this->data['enclosures']; - } - else - { - return null; - } - } - - function get_latitude() - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) - { - return (float) $return[0]['data']; - } - elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) - { - return (float) $match[1]; - } - else - { - return null; - } - } - - function get_longitude() - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) - { - return (float) $return[0]['data']; - } - elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) - { - return (float) $return[0]['data']; - } - elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) - { - return (float) $match[2]; - } - else - { - return null; - } - } - - function get_source() - { - if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'source')) - { - return new $this->feed->source_class($this, $return[0]); - } - else - { - return null; - } - } - - /** - * Creates the add_to_* methods' return data - * - * @access private - * @param string $item_url String to prefix to the item permalink - * @param string $title_url String to prefix to the item title - * (and suffix to the item permalink) - * @return mixed URL if feed exists, false otherwise - */ - function add_to_service($item_url, $title_url = null, $summary_url = null) - { - if ($this->get_permalink() !== null) - { - $return = $item_url . rawurlencode($this->get_permalink()); - if ($title_url !== null && $this->get_title() !== null) - { - $return .= $title_url . rawurlencode($this->get_title()); - } - if ($summary_url !== null && $this->get_description() !== null) - { - $return .= $summary_url . rawurlencode($this->get_description()); - } - return $this->sanitize($return, SIMPLEPIE_CONSTRUCT_IRI); - } - else - { - return null; - } - } - - function add_to_blinklist() - { - return $this->add_to_service('http://www.blinklist.com/index.php?Action=Blink/addblink.php&Description=&Url=', '&Title='); - } - - function add_to_blogmarks() - { - return $this->add_to_service('http://blogmarks.net/my/new.php?mini=1&simple=1&url=', '&title='); - } - - function add_to_delicious() - { - return $this->add_to_service('http://del.icio.us/post/?v=4&url=', '&title='); - } - - function add_to_digg() - { - return $this->add_to_service('http://digg.com/submit?url=', '&title=', '&bodytext='); - } - - function add_to_furl() - { - return $this->add_to_service('http://www.furl.net/storeIt.jsp?u=', '&t='); - } - - function add_to_magnolia() - { - return $this->add_to_service('http://ma.gnolia.com/bookmarklet/add?url=', '&title='); - } - - function add_to_myweb20() - { - return $this->add_to_service('http://myweb2.search.yahoo.com/myresults/bookmarklet?u=', '&t='); - } - - function add_to_newsvine() - { - return $this->add_to_service('http://www.newsvine.com/_wine/save?u=', '&h='); - } - - function add_to_reddit() - { - return $this->add_to_service('http://reddit.com/submit?url=', '&title='); - } - - function add_to_segnalo() - { - return $this->add_to_service('http://segnalo.com/post.html.php?url=', '&title='); - } - - function add_to_simpy() - { - return $this->add_to_service('http://www.simpy.com/simpy/LinkAdd.do?href=', '&title='); - } - - function add_to_spurl() - { - return $this->add_to_service('http://www.spurl.net/spurl.php?v=3&url=', '&title='); - } - - function add_to_wists() - { - return $this->add_to_service('http://wists.com/r.php?c=&r=', '&title='); - } - - function search_technorati() - { - return $this->add_to_service('http://www.technorati.com/search/'); - } -} - -class SimplePie_Source -{ - var $item; - var $data = array(); - - function SimplePie_Source($item, $data) - { - $this->item = $item; - $this->data = $data; - } - - function __toString() - { - return md5(serialize($this->data)); - } - - function get_source_tags($namespace, $tag) - { - if (isset($this->data['child'][$namespace][$tag])) - { - return $this->data['child'][$namespace][$tag]; - } - else - { - return null; - } - } - - function get_base($element = array()) - { - return $this->item->get_base($element); - } - - function sanitize($data, $type, $base = '') - { - return $this->item->sanitize($data, $type, $base); - } - - function get_item() - { - return $this->item; - } - - function get_title() - { - if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_category($key = 0) - { - $categories = $this->get_categories(); - if (isset($categories[$key])) - { - return $categories[$key]; - } - else - { - return null; - } - } - - function get_categories() - { - $categories = array(); - - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category) - { - $term = null; - $scheme = null; - $label = null; - if (isset($category['attribs']['']['term'])) - { - $term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['scheme'])) - { - $scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($category['attribs']['']['label'])) - { - $label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT); - } - $categories[] = new $this->item->feed->category_class($term, $scheme, $label); - } - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category) - { - // This is really the label, but keep this as the term also for BC. - // Label will also work on retrieving because that falls back to term. - $term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT); - if (isset($category['attribs']['']['domain'])) - { - $scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - $scheme = null; - } - $categories[] = new $this->item->feed->category_class($term, $scheme, null); - } - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category) - { - $categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category) - { - $categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - - if (!empty($categories)) - { - return SimplePie_Misc::array_unique($categories); - } - else - { - return null; - } - } - - function get_author($key = 0) - { - $authors = $this->get_authors(); - if (isset($authors[$key])) - { - return $authors[$key]; - } - else - { - return null; - } - } - - function get_authors() - { - $authors = array(); - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author) - { - $name = null; - $uri = null; - $email = null; - $avatar = null; - $name_date = null; - $uri_date = null; - $avatar_date = null; - - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) - { - $name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) - { - $uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); - } - if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) - { - $email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0]['data'])) - { - $avatar = $this->sanitize($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar'][0])); - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['name-updated'][0]['data'])) - { - $name_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['name-updated'][0]['data']; - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['uri-updated'][0]['data'])) - { - $uri_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['uri-updated'][0]['data']; - } - if (isset($author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar-updated'][0]['data'])) - { - $avatar_date = $author['child']['http://purl.org/macgirvin/dfrn/1.0']['avatar-updated'][0]['data']; - } - - if ($name !== null || $email !== null || $uri !== null || $avatar !== null || $name_date !== null || $uri_date !== null || $avatar_date !== null ) - { - $authors[] = new $this->item->feed->author_class($name, $uri, $email, $avatar, $name_date, $uri_date, $avatar_date); - } - } - if ($author = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author')) - { - $name = null; - $url = null; - $email = null; - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) - { - $name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) - { - $url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); - } - if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) - { - $email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $url !== null) - { - $authors[] = new $this->item->feed->author_class($name, $url, $email); - } - } - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author) - { - $authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author) - { - $authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author) - { - $authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null); - } - - if (!empty($authors)) - { - return SimplePie_Misc::array_unique($authors); - } - else - { - return null; - } - } - - function get_contributor($key = 0) - { - $contributors = $this->get_contributors(); - if (isset($contributors[$key])) - { - return $contributors[$key]; - } - else - { - return null; - } - } - - function get_contributors() - { - $contributors = array(); - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor) - { - $name = null; - $uri = null; - $email = null; - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])) - { - $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])) - { - $uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0])); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'])) - { - $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $uri !== null) - { - $contributors[] = new $this->item->feed->author_class($name, $uri, $email); - } - } - foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor) - { - $name = null; - $url = null; - $email = null; - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'])) - { - $name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'])) - { - $url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0])); - } - if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'])) - { - $email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - if ($name !== null || $email !== null || $url !== null) - { - $contributors[] = new $this->item->feed->author_class($name, $url, $email); - } - } - - if (!empty($contributors)) - { - return SimplePie_Misc::array_unique($contributors); - } - else - { - return null; - } - } - - function get_link($key = 0, $rel = 'alternate') - { - $links = $this->get_links($rel); - if (isset($links[$key])) - { - return $links[$key]; - } - else - { - return null; - } - } - - /** - * Added for parity between the parent-level and the item/entry-level. - */ - function get_permalink() - { - return $this->get_link(0); - } - - function get_links($rel = 'alternate') - { - if (!isset($this->data['links'])) - { - $this->data['links'] = array(); - if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link')) - { - foreach ($links as $link) - { - if (isset($link['attribs']['']['href'])) - { - $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; - $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - } - } - } - if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link')) - { - foreach ($links as $link) - { - if (isset($link['attribs']['']['href'])) - { - $link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate'; - $this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link)); - - } - } - } - if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link')) - { - $this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0])); - } - - $keys = array_keys($this->data['links']); - foreach ($keys as $key) - { - if (SimplePie_Misc::is_isegment_nz_nc($key)) - { - if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key])) - { - $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]); - $this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]; - } - else - { - $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key]; - } - } - elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY) - { - $this->data['links'][substr($key, 41)] =& $this->data['links'][$key]; - } - $this->data['links'][$key] = array_unique($this->data['links'][$key]); - } - } - - if (isset($this->data['links'][$rel])) - { - return $this->data['links'][$rel]; - } - else - { - return null; - } - } - - function get_description() - { - if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0])); - } - else - { - return null; - } - } - - function get_copyright() - { - if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright')) - { - return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_language() - { - if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT); - } - elseif (isset($this->data['xml_lang'])) - { - return $this->sanitize($this->data['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT); - } - else - { - return null; - } - } - - function get_latitude() - { - if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat')) - { - return (float) $return[0]['data']; - } - elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) - { - return (float) $match[1]; - } - else - { - return null; - } - } - - function get_longitude() - { - if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long')) - { - return (float) $return[0]['data']; - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon')) - { - return (float) $return[0]['data']; - } - elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match)) - { - return (float) $match[2]; - } - else - { - return null; - } - } - - function get_image_url() - { - if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image')) - { - return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon')) - { - return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0])); - } - else - { - return null; - } - } -} - -class SimplePie_Author -{ - var $name; - var $link; - var $email; - var $avatar; - var $name_date; - var $uri_date; - var $avatar_date; - - // Constructor, used to input the data - function SimplePie_Author($name = null, $link = null, $email = null, $avatar = null, $name_date = null, $uri_date = null, $avatar_date = null) - { - $this->name = $name; - $this->link = $link; - $this->email = $email; - $this->avatar = $avatar; - $this->name_date = $name_date; - $this->uri_date = $uri_date; - $this->avatar_date = $avatar_date; - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_name() - { - if ($this->name !== null) - { - return $this->name; - } - else - { - return null; - } - } - - function get_link() - { - if ($this->link !== null) - { - return $this->link; - } - else - { - return null; - } - } - - function get_email() - { - if ($this->email !== null) - { - return $this->email; - } - else - { - return null; - } - } - - function get_avatar() - { - if ($this->avatar !== null) - { - return $this->avatar; - } - else - { - return null; - } - } - - function get_name_date() - { - if ($this->name_date !== null) - { - return $this->name_date; - } - else - { - return null; - } - } - function get_uri_date() - { - if ($this->uri_date !== null) - { - return $this->uri_date; - } - else - { - return null; - } - } - function get_avatar_date() - { - if ($this->avatar_date !== null) - { - return $this->avatar_date; - } - else - { - return null; - } - } - - -} - -class SimplePie_Category -{ - var $term; - var $scheme; - var $label; - - // Constructor, used to input the data - function SimplePie_Category($term = null, $scheme = null, $label = null) - { - $this->term = $term; - $this->scheme = $scheme; - $this->label = $label; - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_term() - { - if ($this->term !== null) - { - return $this->term; - } - else - { - return null; - } - } - - function get_scheme() - { - if ($this->scheme !== null) - { - return $this->scheme; - } - else - { - return null; - } - } - - function get_label() - { - if ($this->label !== null) - { - return $this->label; - } - else - { - return $this->get_term(); - } - } -} - -class SimplePie_Enclosure -{ - var $bitrate; - var $captions; - var $categories; - var $channels; - var $copyright; - var $credits; - var $description; - var $duration; - var $expression; - var $framerate; - var $handler; - var $hashes; - var $height; - var $javascript; - var $keywords; - var $lang; - var $length; - var $link; - var $medium; - var $player; - var $ratings; - var $restrictions; - var $samplingrate; - var $thumbnails; - var $title; - var $type; - var $width; - - // Constructor, used to input the data - function SimplePie_Enclosure($link = null, $type = null, $length = null, $javascript = null, $bitrate = null, $captions = null, $categories = null, $channels = null, $copyright = null, $credits = null, $description = null, $duration = null, $expression = null, $framerate = null, $hashes = null, $height = null, $keywords = null, $lang = null, $medium = null, $player = null, $ratings = null, $restrictions = null, $samplingrate = null, $thumbnails = null, $title = null, $width = null) - { - $this->bitrate = $bitrate; - $this->captions = $captions; - $this->categories = $categories; - $this->channels = $channels; - $this->copyright = $copyright; - $this->credits = $credits; - $this->description = $description; - $this->duration = $duration; - $this->expression = $expression; - $this->framerate = $framerate; - $this->hashes = $hashes; - $this->height = $height; - $this->javascript = $javascript; - $this->keywords = $keywords; - $this->lang = $lang; - $this->length = $length; - $this->link = $link; - $this->medium = $medium; - $this->player = $player; - $this->ratings = $ratings; - $this->restrictions = $restrictions; - $this->samplingrate = $samplingrate; - $this->thumbnails = $thumbnails; - $this->title = $title; - $this->type = $type; - $this->width = $width; - if (class_exists('idna_convert')) - { - $idn = new idna_convert; - $parsed = SimplePie_Misc::parse_url($link); - $this->link = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); - } - $this->handler = $this->get_handler(); // Needs to load last - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_bitrate() - { - if ($this->bitrate !== null) - { - return $this->bitrate; - } - else - { - return null; - } - } - - function get_caption($key = 0) - { - $captions = $this->get_captions(); - if (isset($captions[$key])) - { - return $captions[$key]; - } - else - { - return null; - } - } - - function get_captions() - { - if ($this->captions !== null) - { - return $this->captions; - } - else - { - return null; - } - } - - function get_category($key = 0) - { - $categories = $this->get_categories(); - if (isset($categories[$key])) - { - return $categories[$key]; - } - else - { - return null; - } - } - - function get_categories() - { - if ($this->categories !== null) - { - return $this->categories; - } - else - { - return null; - } - } - - function get_channels() - { - if ($this->channels !== null) - { - return $this->channels; - } - else - { - return null; - } - } - - function get_copyright() - { - if ($this->copyright !== null) - { - return $this->copyright; - } - else - { - return null; - } - } - - function get_credit($key = 0) - { - $credits = $this->get_credits(); - if (isset($credits[$key])) - { - return $credits[$key]; - } - else - { - return null; - } - } - - function get_credits() - { - if ($this->credits !== null) - { - return $this->credits; - } - else - { - return null; - } - } - - function get_description() - { - if ($this->description !== null) - { - return $this->description; - } - else - { - return null; - } - } - - function get_duration($convert = false) - { - if ($this->duration !== null) - { - if ($convert) - { - $time = SimplePie_Misc::time_hms($this->duration); - return $time; - } - else - { - return $this->duration; - } - } - else - { - return null; - } - } - - function get_expression() - { - if ($this->expression !== null) - { - return $this->expression; - } - else - { - return 'full'; - } - } - - function get_extension() - { - if ($this->link !== null) - { - $url = SimplePie_Misc::parse_url($this->link); - if ($url['path'] !== '') - { - return pathinfo($url['path'], PATHINFO_EXTENSION); - } - } - return null; - } - - function get_framerate() - { - if ($this->framerate !== null) - { - return $this->framerate; - } - else - { - return null; - } - } - - function get_handler() - { - return $this->get_real_type(true); - } - - function get_hash($key = 0) - { - $hashes = $this->get_hashes(); - if (isset($hashes[$key])) - { - return $hashes[$key]; - } - else - { - return null; - } - } - - function get_hashes() - { - if ($this->hashes !== null) - { - return $this->hashes; - } - else - { - return null; - } - } - - function get_height() - { - if ($this->height !== null) - { - return $this->height; - } - else - { - return null; - } - } - - function get_language() - { - if ($this->lang !== null) - { - return $this->lang; - } - else - { - return null; - } - } - - function get_keyword($key = 0) - { - $keywords = $this->get_keywords(); - if (isset($keywords[$key])) - { - return $keywords[$key]; - } - else - { - return null; - } - } - - function get_keywords() - { - if ($this->keywords !== null) - { - return $this->keywords; - } - else - { - return null; - } - } - - function get_length() - { - if ($this->length !== null) - { - return $this->length; - } - else - { - return null; - } - } - - function get_link() - { - if ($this->link !== null) - { - return urldecode($this->link); - } - else - { - return null; - } - } - - function get_medium() - { - if ($this->medium !== null) - { - return $this->medium; - } - else - { - return null; - } - } - - function get_player() - { - if ($this->player !== null) - { - return $this->player; - } - else - { - return null; - } - } - - function get_rating($key = 0) - { - $ratings = $this->get_ratings(); - if (isset($ratings[$key])) - { - return $ratings[$key]; - } - else - { - return null; - } - } - - function get_ratings() - { - if ($this->ratings !== null) - { - return $this->ratings; - } - else - { - return null; - } - } - - function get_restriction($key = 0) - { - $restrictions = $this->get_restrictions(); - if (isset($restrictions[$key])) - { - return $restrictions[$key]; - } - else - { - return null; - } - } - - function get_restrictions() - { - if ($this->restrictions !== null) - { - return $this->restrictions; - } - else - { - return null; - } - } - - function get_sampling_rate() - { - if ($this->samplingrate !== null) - { - return $this->samplingrate; - } - else - { - return null; - } - } - - function get_size() - { - $length = $this->get_length(); - if ($length !== null) - { - return round($length/1048576, 2); - } - else - { - return null; - } - } - - function get_thumbnail($key = 0) - { - $thumbnails = $this->get_thumbnails(); - if (isset($thumbnails[$key])) - { - return $thumbnails[$key]; - } - else - { - return null; - } - } - - function get_thumbnails() - { - if ($this->thumbnails !== null) - { - return $this->thumbnails; - } - else - { - return null; - } - } - - function get_title() - { - if ($this->title !== null) - { - return $this->title; - } - else - { - return null; - } - } - - function get_type() - { - if ($this->type !== null) - { - return $this->type; - } - else - { - return null; - } - } - - function get_width() - { - if ($this->width !== null) - { - return $this->width; - } - else - { - return null; - } - } - - function native_embed($options='') - { - return $this->embed($options, true); - } - - /** - * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'. - */ - function embed($options = '', $native = false) - { - // Set up defaults - $audio = ''; - $video = ''; - $alt = ''; - $altclass = ''; - $loop = 'false'; - $width = 'auto'; - $height = 'auto'; - $bgcolor = '#ffffff'; - $mediaplayer = ''; - $widescreen = false; - $handler = $this->get_handler(); - $type = $this->get_real_type(); - - // Process options and reassign values as necessary - if (is_array($options)) - { - extract($options); - } - else - { - $options = explode(',', $options); - foreach($options as $option) - { - $opt = explode(':', $option, 2); - if (isset($opt[0], $opt[1])) - { - $opt[0] = trim($opt[0]); - $opt[1] = trim($opt[1]); - switch ($opt[0]) - { - case 'audio': - $audio = $opt[1]; - break; - - case 'video': - $video = $opt[1]; - break; - - case 'alt': - $alt = $opt[1]; - break; - - case 'altclass': - $altclass = $opt[1]; - break; - - case 'loop': - $loop = $opt[1]; - break; - - case 'width': - $width = $opt[1]; - break; - - case 'height': - $height = $opt[1]; - break; - - case 'bgcolor': - $bgcolor = $opt[1]; - break; - - case 'mediaplayer': - $mediaplayer = $opt[1]; - break; - - case 'widescreen': - $widescreen = $opt[1]; - break; - } - } - } - } - - $mime = explode('/', $type, 2); - $mime = $mime[0]; - - // Process values for 'auto' - if ($width === 'auto') - { - if ($mime === 'video') - { - if ($height === 'auto') - { - $width = 480; - } - elseif ($widescreen) - { - $width = round((intval($height)/9)*16); - } - else - { - $width = round((intval($height)/3)*4); - } - } - else - { - $width = '100%'; - } - } - - if ($height === 'auto') - { - if ($mime === 'audio') - { - $height = 0; - } - elseif ($mime === 'video') - { - if ($width === 'auto') - { - if ($widescreen) - { - $height = 270; - } - else - { - $height = 360; - } - } - elseif ($widescreen) - { - $height = round((intval($width)/16)*9); - } - else - { - $height = round((intval($width)/4)*3); - } - } - else - { - $height = 376; - } - } - elseif ($mime === 'audio') - { - $height = 0; - } - - // Set proper placeholder value - if ($mime === 'audio') - { - $placeholder = $audio; - } - elseif ($mime === 'video') - { - $placeholder = $video; - } - - $embed = ''; - - // Make sure the JS library is included - if (!$native) - { - static $javascript_outputted = null; - if (!$javascript_outputted && $this->javascript) - { - $embed .= '<script type="text/javascript" src="?' . htmlspecialchars($this->javascript) . '"></script>'; - $javascript_outputted = true; - } - } - - // Odeo Feed MP3's - if ($handler === 'odeo') - { - if ($native) - { - $embed .= '<embed src="http://odeo.com/flash/audio_player_fullsize.swf" pluginspage="http://adobe.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="440" height="80" wmode="transparent" allowScriptAccess="any" flashvars="valid_sample_rate=true&external_url=' . $this->get_link() . '"></embed>'; - } - else - { - $embed .= '<script type="text/javascript">embed_odeo("' . $this->get_link() . '");</script>'; - } - } - - // Flash - elseif ($handler === 'flash') - { - if ($native) - { - $embed .= "<embed src=\"" . $this->get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\"></embed>"; - } - else - { - $embed .= "<script type='text/javascript'>embed_flash('$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$loop', '$type');</script>"; - } - } - - // Flash Media Player file types. - // Preferred handler for MP3 file types. - elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== '')) - { - $height += 20; - if ($native) - { - $embed .= "<embed src=\"$mediaplayer\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" quality=\"high\" width=\"$width\" height=\"$height\" wmode=\"transparent\" flashvars=\"file=" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\"></embed>"; - } - else - { - $embed .= "<script type='text/javascript'>embed_flv('$width', '$height', '" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "', '$placeholder', '$loop', '$mediaplayer');</script>"; - } - } - - // QuickTime 7 file types. Need to test with QuickTime 6. - // Only handle MP3's if the Flash Media Player is not present. - elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === '')) - { - $height += 16; - if ($native) - { - if ($placeholder !== '') - { - $embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" href=\"" . $this->get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>"; - } - else - { - $embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" src=\"" . $this->get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>"; - } - } - else - { - $embed .= "<script type='text/javascript'>embed_quicktime('$type', '$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$placeholder', '$loop');</script>"; - } - } - - // Windows Media - elseif ($handler === 'wmedia') - { - $height += 45; - if ($native) - { - $embed .= "<embed type=\"application/x-mplayer2\" src=\"" . $this->get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\"></embed>"; - } - else - { - $embed .= "<script type='text/javascript'>embed_wmedia('$width', '$height', '" . $this->get_link() . "');</script>"; - } - } - - // Everything else - else $embed .= '<a href="' . $this->get_link() . '" class="' . $altclass . '">' . $alt . '</a>'; - - return $embed; - } - - function get_real_type($find_handler = false) - { - // If it's Odeo, let's get it out of the way. - if (substr(strtolower($this->get_link()), 0, 15) === 'http://odeo.com') - { - return 'odeo'; - } - - // Mime-types by handler. - $types_flash = array('application/x-shockwave-flash', 'application/futuresplash'); // Flash - $types_fmedia = array('video/flv', 'video/x-flv','flv-application/octet-stream'); // Flash Media Player - $types_quicktime = array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'); // QuickTime - $types_wmedia = array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'); // Windows Media - $types_mp3 = array('audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg'); // MP3 - - if ($this->get_type() !== null) - { - $type = strtolower($this->type); - } - else - { - $type = null; - } - - // If we encounter an unsupported mime-type, check the file extension and guess intelligently. - if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3))) - { - switch (strtolower($this->get_extension())) - { - // Audio mime-types - case 'aac': - case 'adts': - $type = 'audio/acc'; - break; - - case 'aif': - case 'aifc': - case 'aiff': - case 'cdda': - $type = 'audio/aiff'; - break; - - case 'bwf': - $type = 'audio/wav'; - break; - - case 'kar': - case 'mid': - case 'midi': - case 'smf': - $type = 'audio/midi'; - break; - - case 'm4a': - $type = 'audio/x-m4a'; - break; - - case 'mp3': - case 'swa': - $type = 'audio/mp3'; - break; - - case 'wav': - $type = 'audio/wav'; - break; - - case 'wax': - $type = 'audio/x-ms-wax'; - break; - - case 'wma': - $type = 'audio/x-ms-wma'; - break; - - // Video mime-types - case '3gp': - case '3gpp': - $type = 'video/3gpp'; - break; - - case '3g2': - case '3gp2': - $type = 'video/3gpp2'; - break; - - case 'asf': - $type = 'video/x-ms-asf'; - break; - - case 'flv': - $type = 'video/x-flv'; - break; - - case 'm1a': - case 'm1s': - case 'm1v': - case 'm15': - case 'm75': - case 'mp2': - case 'mpa': - case 'mpeg': - case 'mpg': - case 'mpm': - case 'mpv': - $type = 'video/mpeg'; - break; - - case 'm4v': - $type = 'video/x-m4v'; - break; - - case 'mov': - case 'qt': - $type = 'video/quicktime'; - break; - - case 'mp4': - case 'mpg4': - $type = 'video/mp4'; - break; - - case 'sdv': - $type = 'video/sd-video'; - break; - - case 'wm': - $type = 'video/x-ms-wm'; - break; - - case 'wmv': - $type = 'video/x-ms-wmv'; - break; - - case 'wvx': - $type = 'video/x-ms-wvx'; - break; - - // Flash mime-types - case 'spl': - $type = 'application/futuresplash'; - break; - - case 'swf': - $type = 'application/x-shockwave-flash'; - break; - } - } - - if ($find_handler) - { - if (in_array($type, $types_flash)) - { - return 'flash'; - } - elseif (in_array($type, $types_fmedia)) - { - return 'fmedia'; - } - elseif (in_array($type, $types_quicktime)) - { - return 'quicktime'; - } - elseif (in_array($type, $types_wmedia)) - { - return 'wmedia'; - } - elseif (in_array($type, $types_mp3)) - { - return 'mp3'; - } - else - { - return null; - } - } - else - { - return $type; - } - } -} - -class SimplePie_Caption -{ - var $type; - var $lang; - var $startTime; - var $endTime; - var $text; - - // Constructor, used to input the data - function SimplePie_Caption($type = null, $lang = null, $startTime = null, $endTime = null, $text = null) - { - $this->type = $type; - $this->lang = $lang; - $this->startTime = $startTime; - $this->endTime = $endTime; - $this->text = $text; - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_endtime() - { - if ($this->endTime !== null) - { - return $this->endTime; - } - else - { - return null; - } - } - - function get_language() - { - if ($this->lang !== null) - { - return $this->lang; - } - else - { - return null; - } - } - - function get_starttime() - { - if ($this->startTime !== null) - { - return $this->startTime; - } - else - { - return null; - } - } - - function get_text() - { - if ($this->text !== null) - { - return $this->text; - } - else - { - return null; - } - } - - function get_type() - { - if ($this->type !== null) - { - return $this->type; - } - else - { - return null; - } - } -} - -class SimplePie_Credit -{ - var $role; - var $scheme; - var $name; - - // Constructor, used to input the data - function SimplePie_Credit($role = null, $scheme = null, $name = null) - { - $this->role = $role; - $this->scheme = $scheme; - $this->name = $name; - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_role() - { - if ($this->role !== null) - { - return $this->role; - } - else - { - return null; - } - } - - function get_scheme() - { - if ($this->scheme !== null) - { - return $this->scheme; - } - else - { - return null; - } - } - - function get_name() - { - if ($this->name !== null) - { - return $this->name; - } - else - { - return null; - } - } -} - -class SimplePie_Copyright -{ - var $url; - var $label; - - // Constructor, used to input the data - function SimplePie_Copyright($url = null, $label = null) - { - $this->url = $url; - $this->label = $label; - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_url() - { - if ($this->url !== null) - { - return $this->url; - } - else - { - return null; - } - } - - function get_attribution() - { - if ($this->label !== null) - { - return $this->label; - } - else - { - return null; - } - } -} - -class SimplePie_Rating -{ - var $scheme; - var $value; - - // Constructor, used to input the data - function SimplePie_Rating($scheme = null, $value = null) - { - $this->scheme = $scheme; - $this->value = $value; - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_scheme() - { - if ($this->scheme !== null) - { - return $this->scheme; - } - else - { - return null; - } - } - - function get_value() - { - if ($this->value !== null) - { - return $this->value; - } - else - { - return null; - } - } -} - -class SimplePie_Restriction -{ - var $relationship; - var $type; - var $value; - - // Constructor, used to input the data - function SimplePie_Restriction($relationship = null, $type = null, $value = null) - { - $this->relationship = $relationship; - $this->type = $type; - $this->value = $value; - } - - function __toString() - { - // There is no $this->data here - return md5(serialize($this)); - } - - function get_relationship() - { - if ($this->relationship !== null) - { - return $this->relationship; - } - else - { - return null; - } - } - - function get_type() - { - if ($this->type !== null) - { - return $this->type; - } - else - { - return null; - } - } - - function get_value() - { - if ($this->value !== null) - { - return $this->value; - } - else - { - return null; - } - } -} - -/** - * @todo Move to properly supporting RFC2616 (HTTP/1.1) - */ -class SimplePie_File -{ - var $url; - var $useragent; - var $success = true; - var $headers = array(); - var $body; - var $status_code; - var $redirects = 0; - var $error; - var $method = SIMPLEPIE_FILE_SOURCE_NONE; - - function SimplePie_File($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) - { - if (class_exists('idna_convert')) - { - $idn = new idna_convert; - $parsed = SimplePie_Misc::parse_url($url); - $url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); - } - $this->url = $url; - $this->useragent = $useragent; - if (preg_match('/^http(s)?:\/\//i', $url)) - { - if ($useragent === null) - { - $useragent = ini_get('user_agent'); - $this->useragent = $useragent; - } - if (!is_array($headers)) - { - $headers = array(); - } - if (!$force_fsockopen && function_exists('curl_exec')) - { - $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL; - $fp = curl_init(); - $headers2 = array(); - foreach ($headers as $key => $value) - { - $headers2[] = "$key: $value"; - } - if (version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>=')) - { - curl_setopt($fp, CURLOPT_ENCODING, ''); - } - curl_setopt($fp, CURLOPT_URL, $url); - curl_setopt($fp, CURLOPT_HEADER, 1); - curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($fp, CURLOPT_TIMEOUT, $timeout); - curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout); - curl_setopt($fp, CURLOPT_REFERER, $url); - curl_setopt($fp, CURLOPT_USERAGENT, $useragent); - curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); - if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>=')) - { - curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects); - } - - $this->headers = curl_exec($fp); - if (curl_errno($fp) === 23 || curl_errno($fp) === 61) - { - curl_setopt($fp, CURLOPT_ENCODING, 'none'); - $this->headers = curl_exec($fp); - } - if (curl_errno($fp)) - { - $this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp); - $this->success = false; - } - else - { - $info = curl_getinfo($fp); - curl_close($fp); - $this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 1); - $this->headers = array_pop($this->headers); - $parser = new SimplePie_HTTP_Parser($this->headers); - if ($parser->parse()) - { - $this->headers = $parser->headers; - $this->body = $parser->body; - $this->status_code = $parser->status_code; - if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) - { - $this->redirects++; - $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); - return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); - } - } - } - } - else - { - $this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN; - $url_parts = parse_url($url); - if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') - { - $url_parts['host'] = "ssl://$url_parts[host]"; - $url_parts['port'] = 443; - } - if (!isset($url_parts['port'])) - { - $url_parts['port'] = 80; - } - $fp = @fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout); - if (!$fp) - { - $this->error = 'fsockopen error: ' . $errstr; - $this->success = false; - } - else - { - stream_set_timeout($fp, $timeout); - if (isset($url_parts['path'])) - { - if (isset($url_parts['query'])) - { - $get = "$url_parts[path]?$url_parts[query]"; - } - else - { - $get = $url_parts['path']; - } - } - else - { - $get = '/'; - } - $out = "GET $get HTTP/1.0\r\n"; - $out .= "Host: $url_parts[host]\r\n"; - $out .= "User-Agent: $useragent\r\n"; - if (extension_loaded('zlib')) - { - $out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n"; - } - - if (isset($url_parts['user']) && isset($url_parts['pass'])) - { - $out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n"; - } - foreach ($headers as $key => $value) - { - $out .= "$key: $value\r\n"; - } - $out .= "Connection: Close\r\n\r\n"; - fwrite($fp, $out); - - $info = stream_get_meta_data($fp); - - $this->headers = ''; - while (!$info['eof'] && !$info['timed_out']) - { - $this->headers .= fread($fp, 1160); - $info = stream_get_meta_data($fp); - } - if (!$info['timed_out']) - { - $parser = new SimplePie_HTTP_Parser($this->headers); - if ($parser->parse()) - { - $this->headers = $parser->headers; - $this->body = $parser->body; - $this->status_code = $parser->status_code; - if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects) - { - $this->redirects++; - $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); - return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); - } - if (isset($this->headers['content-encoding'])) - { - // Hey, we act dumb elsewhere, so let's do that here too - switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20"))) - { - case 'gzip': - case 'x-gzip': - $decoder = new SimplePie_gzdecode($this->body); - if (!$decoder->parse()) - { - $this->error = 'Unable to decode HTTP "gzip" stream'; - $this->success = false; - } - else - { - $this->body = $decoder->data; - } - break; - - case 'deflate': - if (($body = gzuncompress($this->body)) === false) - { - if (($body = gzinflate($this->body)) === false) - { - $this->error = 'Unable to decode HTTP "deflate" stream'; - $this->success = false; - } - } - $this->body = $body; - break; - - default: - $this->error = 'Unknown content coding'; - $this->success = false; - } - } - } - } - else - { - $this->error = 'fsocket timed out'; - $this->success = false; - } - fclose($fp); - } - } - } - else - { - $this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS; - if (!$this->body = file_get_contents($url)) - { - $this->error = 'file_get_contents could not read the file'; - $this->success = false; - } - } - } -} - -/** - * HTTP Response Parser - * - * @package SimplePie - */ -class SimplePie_HTTP_Parser -{ - /** - * HTTP Version - * - * @access public - * @var float - */ - var $http_version = 0.0; - - /** - * Status code - * - * @access public - * @var int - */ - var $status_code = 0; - - /** - * Reason phrase - * - * @access public - * @var string - */ - var $reason = ''; - - /** - * Key/value pairs of the headers - * - * @access public - * @var array - */ - var $headers = array(); - - /** - * Body of the response - * - * @access public - * @var string - */ - var $body = ''; - - /** - * Current state of the state machine - * - * @access private - * @var string - */ - var $state = 'http_version'; - - /** - * Input data - * - * @access private - * @var string - */ - var $data = ''; - - /** - * Input data length (to avoid calling strlen() everytime this is needed) - * - * @access private - * @var int - */ - var $data_length = 0; - - /** - * Current position of the pointer - * - * @var int - * @access private - */ - var $position = 0; - - /** - * Name of the hedaer currently being parsed - * - * @access private - * @var string - */ - var $name = ''; - - /** - * Value of the hedaer currently being parsed - * - * @access private - * @var string - */ - var $value = ''; - - /** - * Create an instance of the class with the input data - * - * @access public - * @param string $data Input data - */ - function SimplePie_HTTP_Parser($data) - { - $this->data = $data; - $this->data_length = strlen($this->data); - } - - /** - * Parse the input data - * - * @access public - * @return bool true on success, false on failure - */ - function parse() - { - while ($this->state && $this->state !== 'emit' && $this->has_data()) - { - $state = $this->state; - $this->$state(); - } - $this->data = ''; - if ($this->state === 'emit' || $this->state === 'body') - { - return true; - } - else - { - $this->http_version = ''; - $this->status_code = ''; - $this->reason = ''; - $this->headers = array(); - $this->body = ''; - return false; - } - } - - /** - * Check whether there is data beyond the pointer - * - * @access private - * @return bool true if there is further data, false if not - */ - function has_data() - { - return (bool) ($this->position < $this->data_length); - } - - /** - * See if the next character is LWS - * - * @access private - * @return bool true if the next character is LWS, false if not - */ - function is_linear_whitespace() - { - return (bool) ($this->data[$this->position] === "\x09" - || $this->data[$this->position] === "\x20" - || ($this->data[$this->position] === "\x0A" - && isset($this->data[$this->position + 1]) - && ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20"))); - } - - /** - * Parse the HTTP version - * - * @access private - */ - function http_version() - { - if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/') - { - $len = strspn($this->data, '0123456789.', 5); - $this->http_version = substr($this->data, 5, $len); - $this->position += 5 + $len; - if (substr_count($this->http_version, '.') <= 1) - { - $this->http_version = (float) $this->http_version; - $this->position += strspn($this->data, "\x09\x20", $this->position); - $this->state = 'status'; - } - else - { - $this->state = false; - } - } - else - { - $this->state = false; - } - } - - /** - * Parse the status code - * - * @access private - */ - function status() - { - if ($len = strspn($this->data, '0123456789', $this->position)) - { - $this->status_code = (int) substr($this->data, $this->position, $len); - $this->position += $len; - $this->state = 'reason'; - } - else - { - $this->state = false; - } - } - - /** - * Parse the reason phrase - * - * @access private - */ - function reason() - { - $len = strcspn($this->data, "\x0A", $this->position); - $this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20"); - $this->position += $len + 1; - $this->state = 'new_line'; - } - - /** - * Deal with a new line, shifting data around as needed - * - * @access private - */ - function new_line() - { - $this->value = trim($this->value, "\x0D\x20"); - if ($this->name !== '' && $this->value !== '') - { - $this->name = strtolower($this->name); - if (isset($this->headers[$this->name])) - { - $this->headers[$this->name] .= ', ' . $this->value; - } - else - { - $this->headers[$this->name] = $this->value; - } - } - $this->name = ''; - $this->value = ''; - if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A") - { - $this->position += 2; - $this->state = 'body'; - } - elseif ($this->data[$this->position] === "\x0A") - { - $this->position++; - $this->state = 'body'; - } - else - { - $this->state = 'name'; - } - } - - /** - * Parse a header name - * - * @access private - */ - function name() - { - $len = strcspn($this->data, "\x0A:", $this->position); - if (isset($this->data[$this->position + $len])) - { - if ($this->data[$this->position + $len] === "\x0A") - { - $this->position += $len; - $this->state = 'new_line'; - } - else - { - $this->name = substr($this->data, $this->position, $len); - $this->position += $len + 1; - $this->state = 'value'; - } - } - else - { - $this->state = false; - } - } - - /** - * Parse LWS, replacing consecutive LWS characters with a single space - * - * @access private - */ - function linear_whitespace() - { - do - { - if (substr($this->data, $this->position, 2) === "\x0D\x0A") - { - $this->position += 2; - } - elseif ($this->data[$this->position] === "\x0A") - { - $this->position++; - } - $this->position += strspn($this->data, "\x09\x20", $this->position); - } while ($this->has_data() && $this->is_linear_whitespace()); - $this->value .= "\x20"; - } - - /** - * See what state to move to while within non-quoted header values - * - * @access private - */ - function value() - { - if ($this->is_linear_whitespace()) - { - $this->linear_whitespace(); - } - else - { - switch ($this->data[$this->position]) - { - case '"': - $this->position++; - $this->state = 'quote'; - break; - - case "\x0A": - $this->position++; - $this->state = 'new_line'; - break; - - default: - $this->state = 'value_char'; - break; - } - } - } - - /** - * Parse a header value while outside quotes - * - * @access private - */ - function value_char() - { - $len = strcspn($this->data, "\x09\x20\x0A\"", $this->position); - $this->value .= substr($this->data, $this->position, $len); - $this->position += $len; - $this->state = 'value'; - } - - /** - * See what state to move to while within quoted header values - * - * @access private - */ - function quote() - { - if ($this->is_linear_whitespace()) - { - $this->linear_whitespace(); - } - else - { - switch ($this->data[$this->position]) - { - case '"': - $this->position++; - $this->state = 'value'; - break; - - case "\x0A": - $this->position++; - $this->state = 'new_line'; - break; - - case '\\': - $this->position++; - $this->state = 'quote_escaped'; - break; - - default: - $this->state = 'quote_char'; - break; - } - } - } - - /** - * Parse a header value while within quotes - * - * @access private - */ - function quote_char() - { - $len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position); - $this->value .= substr($this->data, $this->position, $len); - $this->position += $len; - $this->state = 'value'; - } - - /** - * Parse an escaped character within quotes - * - * @access private - */ - function quote_escaped() - { - $this->value .= $this->data[$this->position]; - $this->position++; - $this->state = 'quote'; - } - - /** - * Parse the body - * - * @access private - */ - function body() - { - $this->body = substr($this->data, $this->position); - $this->state = 'emit'; - } -} - -/** - * gzdecode - * - * @package SimplePie - */ -class SimplePie_gzdecode -{ - /** - * Compressed data - * - * @access private - * @see gzdecode::$data - */ - var $compressed_data; - - /** - * Size of compressed data - * - * @access private - */ - var $compressed_size; - - /** - * Minimum size of a valid gzip string - * - * @access private - */ - var $min_compressed_size = 18; - - /** - * Current position of pointer - * - * @access private - */ - var $position = 0; - - /** - * Flags (FLG) - * - * @access private - */ - var $flags; - - /** - * Uncompressed data - * - * @access public - * @see gzdecode::$compressed_data - */ - var $data; - - /** - * Modified time - * - * @access public - */ - var $MTIME; - - /** - * Extra Flags - * - * @access public - */ - var $XFL; - - /** - * Operating System - * - * @access public - */ - var $OS; - - /** - * Subfield ID 1 - * - * @access public - * @see gzdecode::$extra_field - * @see gzdecode::$SI2 - */ - var $SI1; - - /** - * Subfield ID 2 - * - * @access public - * @see gzdecode::$extra_field - * @see gzdecode::$SI1 - */ - var $SI2; - - /** - * Extra field content - * - * @access public - * @see gzdecode::$SI1 - * @see gzdecode::$SI2 - */ - var $extra_field; - - /** - * Original filename - * - * @access public - */ - var $filename; - - /** - * Human readable comment - * - * @access public - */ - var $comment; - - /** - * Don't allow anything to be set - * - * @access public - */ - function __set($name, $value) - { - trigger_error("Cannot write property $name", E_USER_ERROR); - } - - /** - * Set the compressed string and related properties - * - * @access public - */ - function SimplePie_gzdecode($data) - { - $this->compressed_data = $data; - $this->compressed_size = strlen($data); - } - - /** - * Decode the GZIP stream - * - * @access public - */ - function parse() - { - if ($this->compressed_size >= $this->min_compressed_size) - { - // Check ID1, ID2, and CM - if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08") - { - return false; - } - - // Get the FLG (FLaGs) - $this->flags = ord($this->compressed_data[3]); - - // FLG bits above (1 << 4) are reserved - if ($this->flags > 0x1F) - { - return false; - } - - // Advance the pointer after the above - $this->position += 4; - - // MTIME - $mtime = substr($this->compressed_data, $this->position, 4); - // Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness - if (current(unpack('S', "\x00\x01")) === 1) - { - $mtime = strrev($mtime); - } - $this->MTIME = current(unpack('l', $mtime)); - $this->position += 4; - - // Get the XFL (eXtra FLags) - $this->XFL = ord($this->compressed_data[$this->position++]); - - // Get the OS (Operating System) - $this->OS = ord($this->compressed_data[$this->position++]); - - // Parse the FEXTRA - if ($this->flags & 4) - { - // Read subfield IDs - $this->SI1 = $this->compressed_data[$this->position++]; - $this->SI2 = $this->compressed_data[$this->position++]; - - // SI2 set to zero is reserved for future use - if ($this->SI2 === "\x00") - { - return false; - } - - // Get the length of the extra field - $len = current(unpack('v', substr($this->compressed_data, $this->position, 2))); - $position += 2; - - // Check the length of the string is still valid - $this->min_compressed_size += $len + 4; - if ($this->compressed_size >= $this->min_compressed_size) - { - // Set the extra field to the given data - $this->extra_field = substr($this->compressed_data, $this->position, $len); - $this->position += $len; - } - else - { - return false; - } - } - - // Parse the FNAME - if ($this->flags & 8) - { - // Get the length of the filename - $len = strcspn($this->compressed_data, "\x00", $this->position); - - // Check the length of the string is still valid - $this->min_compressed_size += $len + 1; - if ($this->compressed_size >= $this->min_compressed_size) - { - // Set the original filename to the given string - $this->filename = substr($this->compressed_data, $this->position, $len); - $this->position += $len + 1; - } - else - { - return false; - } - } - - // Parse the FCOMMENT - if ($this->flags & 16) - { - // Get the length of the comment - $len = strcspn($this->compressed_data, "\x00", $this->position); - - // Check the length of the string is still valid - $this->min_compressed_size += $len + 1; - if ($this->compressed_size >= $this->min_compressed_size) - { - // Set the original comment to the given string - $this->comment = substr($this->compressed_data, $this->position, $len); - $this->position += $len + 1; - } - else - { - return false; - } - } - - // Parse the FHCRC - if ($this->flags & 2) - { - // Check the length of the string is still valid - $this->min_compressed_size += $len + 2; - if ($this->compressed_size >= $this->min_compressed_size) - { - // Read the CRC - $crc = current(unpack('v', substr($this->compressed_data, $this->position, 2))); - - // Check the CRC matches - if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc) - { - $this->position += 2; - } - else - { - return false; - } - } - else - { - return false; - } - } - - // Decompress the actual data - if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false) - { - return false; - } - else - { - $this->position = $this->compressed_size - 8; - } - - // Check CRC of data - $crc = current(unpack('V', substr($this->compressed_data, $this->position, 4))); - $this->position += 4; - /*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc)) - { - return false; - }*/ - - // Check ISIZE of data - $isize = current(unpack('V', substr($this->compressed_data, $this->position, 4))); - $this->position += 4; - if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize)) - { - return false; - } - - // Wow, against all odds, we've actually got a valid gzip string - return true; - } - else - { - return false; - } - } -} - -class SimplePie_Cache -{ - /** - * Don't call the constructor. Please. - * - * @access private - */ - function SimplePie_Cache() - { - trigger_error('Please call SimplePie_Cache::create() instead of the constructor', E_USER_ERROR); - } - - /** - * Create a new SimplePie_Cache object - * - * @static - * @access public - */ - function create($location, $filename, $extension) - { - $location_iri = new SimplePie_IRI($location); - switch ($location_iri->get_scheme()) - { - case 'mysql': - if (extension_loaded('mysql')) - { - return new SimplePie_Cache_MySQL($location_iri, $filename, $extension); - } - break; - - default: - return new SimplePie_Cache_File($location, $filename, $extension); - } - } -} - -class SimplePie_Cache_File -{ - var $location; - var $filename; - var $extension; - var $name; - - function SimplePie_Cache_File($location, $filename, $extension) - { - $this->location = $location; - $this->filename = $filename; - $this->extension = $extension; - $this->name = "$this->location/$this->filename.$this->extension"; - } - - function save($data) - { - if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location)) - { - if (is_a($data, 'SimplePie')) - { - $data = $data->data; - } - - $data = serialize($data); - - if (function_exists('file_put_contents')) - { - return (bool) file_put_contents($this->name, $data); - } - else - { - $fp = fopen($this->name, 'wb'); - if ($fp) - { - fwrite($fp, $data); - fclose($fp); - return true; - } - } - } - return false; - } - - function load() - { - if (file_exists($this->name) && is_readable($this->name)) - { - return unserialize(file_get_contents($this->name)); - } - return false; - } - - function mtime() - { - if (file_exists($this->name)) - { - return filemtime($this->name); - } - return false; - } - - function touch() - { - if (file_exists($this->name)) - { - return touch($this->name); - } - return false; - } - - function unlink() - { - if (file_exists($this->name)) - { - return unlink($this->name); - } - return false; - } -} - -class SimplePie_Cache_DB -{ - function prepare_simplepie_object_for_cache($data) - { - $items = $data->get_items(); - $items_by_id = array(); - - if (!empty($items)) - { - foreach ($items as $item) - { - $items_by_id[$item->get_id()] = $item; - } - - if (count($items_by_id) !== count($items)) - { - $items_by_id = array(); - foreach ($items as $item) - { - $items_by_id[$item->get_id(true)] = $item; - } - } - - if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) - { - $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; - } - elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) - { - $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; - } - elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) - { - $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; - } - elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0])) - { - $channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]; - } - else - { - $channel = null; - } - - if ($channel !== null) - { - if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'])) - { - unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']); - } - if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry'])) - { - unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']); - } - if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item'])) - { - unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']); - } - if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item'])) - { - unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']); - } - if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item'])) - { - unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']); - } - } - if (isset($data->data['items'])) - { - unset($data->data['items']); - } - if (isset($data->data['ordered_items'])) - { - unset($data->data['ordered_items']); - } - } - return array(serialize($data->data), $items_by_id); - } -} - -class SimplePie_Cache_MySQL extends SimplePie_Cache_DB -{ - var $mysql; - var $options; - var $id; - - function SimplePie_Cache_MySQL($mysql_location, $name, $extension) - { - $host = $mysql_location->get_host(); - if (SimplePie_Misc::stripos($host, 'unix(') === 0 && substr($host, -1) === ')') - { - $server = ':' . substr($host, 5, -1); - } - else - { - $server = $host; - if ($mysql_location->get_port() !== null) - { - $server .= ':' . $mysql_location->get_port(); - } - } - - if (strpos($mysql_location->get_userinfo(), ':') !== false) - { - list($username, $password) = explode(':', $mysql_location->get_userinfo(), 2); - } - else - { - $username = $mysql_location->get_userinfo(); - $password = null; - } - - if ($this->mysql = mysql_connect($server, $username, $password)) - { - $this->id = $name . $extension; - $this->options = SimplePie_Misc::parse_str($mysql_location->get_query()); - if (!isset($this->options['prefix'][0])) - { - $this->options['prefix'][0] = ''; - } - - if (mysql_select_db(ltrim($mysql_location->get_path(), '/')) - && mysql_query('SET NAMES utf8') - && ($query = mysql_unbuffered_query('SHOW TABLES'))) - { - $db = array(); - while ($row = mysql_fetch_row($query)) - { - $db[] = $row[0]; - } - - if (!in_array($this->options['prefix'][0] . 'cache_data', $db)) - { - if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))')) - { - $this->mysql = null; - } - } - - if (!in_array($this->options['prefix'][0] . 'items', $db)) - { - if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))')) - { - $this->mysql = null; - } - } - } - else - { - $this->mysql = null; - } - } - } - - function save($data) - { - if ($this->mysql) - { - $feed_id = "'" . mysql_real_escape_string($this->id) . "'"; - - if (is_a($data, 'SimplePie')) - { - if (SIMPLEPIE_PHP5) - { - // This keyword needs to defy coding standards for PHP4 compatibility - $data = clone($data); - } - - $prepared = $this->prepare_simplepie_object_for_cache($data); - - if ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql)) - { - if (mysql_num_rows($query)) - { - $items = count($prepared[1]); - if ($items) - { - $sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = ' . $items . ', `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id; - } - else - { - $sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id; - } - - if (!mysql_query($sql, $this->mysql)) - { - return false; - } - } - elseif (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(' . $feed_id . ', ' . count($prepared[1]) . ', \'' . mysql_real_escape_string($prepared[0]) . '\', ' . time() . ')', $this->mysql)) - { - return false; - } - - $ids = array_keys($prepared[1]); - if (!empty($ids)) - { - foreach ($ids as $id) - { - $database_ids[] = mysql_real_escape_string($id); - } - - if ($query = mysql_unbuffered_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'items` WHERE `id` = \'' . implode('\' OR `id` = \'', $database_ids) . '\' AND `feed_id` = ' . $feed_id, $this->mysql)) - { - $existing_ids = array(); - while ($row = mysql_fetch_row($query)) - { - $existing_ids[] = $row[0]; - } - - $new_ids = array_diff($ids, $existing_ids); - - foreach ($new_ids as $new_id) - { - if (!($date = $prepared[1][$new_id]->get_date('U'))) - { - $date = time(); - } - - if (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(' . $feed_id . ', \'' . mysql_real_escape_string($new_id) . '\', \'' . mysql_real_escape_string(serialize($prepared[1][$new_id]->data)) . '\', ' . $date . ')', $this->mysql)) - { - return false; - } - } - return true; - } - } - else - { - return true; - } - } - } - elseif ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql)) - { - if (mysql_num_rows($query)) - { - if (mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = 0, `data` = \'' . mysql_real_escape_string(serialize($data)) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id, $this->mysql)) - { - return true; - } - } - elseif (mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(\'' . mysql_real_escape_string($this->id) . '\', 0, \'' . mysql_real_escape_string(serialize($data)) . '\', ' . time() . ')', $this->mysql)) - { - return true; - } - } - } - return false; - } - - function load() - { - if ($this->mysql && ($query = mysql_query('SELECT `items`, `data` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query))) - { - $data = unserialize($row[1]); - - if (isset($this->options['items'][0])) - { - $items = (int) $this->options['items'][0]; - } - else - { - $items = (int) $row[0]; - } - - if ($items !== 0) - { - if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0])) - { - $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]; - } - elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0])) - { - $feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]; - } - elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0])) - { - $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]; - } - elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0])) - { - $feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]; - } - else - { - $feed = null; - } - - if ($feed !== null) - { - $sql = 'SELECT `data` FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . '\' ORDER BY `posted` DESC'; - if ($items > 0) - { - $sql .= ' LIMIT ' . $items; - } - - if ($query = mysql_unbuffered_query($sql, $this->mysql)) - { - while ($row = mysql_fetch_row($query)) - { - $feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row[0]); - } - } - else - { - return false; - } - } - } - return $data; - } - return false; - } - - function mtime() - { - if ($this->mysql && ($query = mysql_query('SELECT `mtime` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query))) - { - return $row[0]; - } - else - { - return false; - } - } - - function touch() - { - if ($this->mysql && ($query = mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `mtime` = ' . time() . ' WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && mysql_affected_rows($this->mysql)) - { - return true; - } - else - { - return false; - } - } - - function unlink() - { - if ($this->mysql && ($query = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($query2 = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql))) - { - return true; - } - else - { - return false; - } - } -} - -class SimplePie_Misc -{ - function time_hms($seconds) - { - $time = ''; - - $hours = floor($seconds / 3600); - $remainder = $seconds % 3600; - if ($hours > 0) - { - $time .= $hours.':'; - } - - $minutes = floor($remainder / 60); - $seconds = $remainder % 60; - if ($minutes < 10 && $hours > 0) - { - $minutes = '0' . $minutes; - } - if ($seconds < 10) - { - $seconds = '0' . $seconds; - } - - $time .= $minutes.':'; - $time .= $seconds; - - return $time; - } - - function absolutize_url($relative, $base) - { -return $relative; - $iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative); - return $iri->get_iri(); - } - - function remove_dot_segments($input) - { - $output = ''; - while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') - { - // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, - if (strpos($input, '../') === 0) - { - $input = substr($input, 3); - } - elseif (strpos($input, './') === 0) - { - $input = substr($input, 2); - } - // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, - elseif (strpos($input, '/./') === 0) - { - $input = substr_replace($input, '/', 0, 3); - } - elseif ($input === '/.') - { - $input = '/'; - } - // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, - elseif (strpos($input, '/../') === 0) - { - $input = substr_replace($input, '/', 0, 4); - $output = substr_replace($output, '', strrpos($output, '/')); - } - elseif ($input === '/..') - { - $input = '/'; - $output = substr_replace($output, '', strrpos($output, '/')); - } - // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, - elseif ($input === '.' || $input === '..') - { - $input = ''; - } - // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer - elseif (($pos = strpos($input, '/', 1)) !== false) - { - $output .= substr($input, 0, $pos); - $input = substr_replace($input, '', 0, $pos); - } - else - { - $output .= $input; - $input = ''; - } - } - return $output . $input; - } - - function get_element($realname, $string) - { - $return = array(); - $name = preg_quote($realname, '/'); - if (preg_match_all("/<($name)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) - { - for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++) - { - $return[$i]['tag'] = $realname; - $return[$i]['full'] = $matches[$i][0][0]; - $return[$i]['offset'] = $matches[$i][0][1]; - if (strlen($matches[$i][3][0]) <= 2) - { - $return[$i]['self_closing'] = true; - } - else - { - $return[$i]['self_closing'] = false; - $return[$i]['content'] = $matches[$i][4][0]; - } - $return[$i]['attribs'] = array(); - if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER)) - { - for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++) - { - if (count($attribs[$j]) === 2) - { - $attribs[$j][2] = $attribs[$j][1]; - } - $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); - } - } - } - } - return $return; - } - - function element_implode($element) - { - $full = "<$element[tag]"; - foreach ($element['attribs'] as $key => $value) - { - $key = strtolower($key); - $full .= " $key=\"" . htmlspecialchars($value['data']) . '"'; - } - if ($element['self_closing']) - { - $full .= ' />'; - } - else - { - $full .= ">$element[content]</$element[tag]>"; - } - return $full; - } - - function error($message, $level, $file, $line) - { - if ((ini_get('error_reporting') & $level) > 0) - { - switch ($level) - { - case E_USER_ERROR: - $note = 'PHP Error'; - break; - case E_USER_WARNING: - $note = 'PHP Warning'; - break; - case E_USER_NOTICE: - $note = 'PHP Notice'; - break; - default: - $note = 'Unknown Error'; - break; - } - - $log_error = true; - if (!function_exists('error_log')) - { - $log_error = false; - } - - $log_file = @ini_get('error_log'); - if (!empty($log_file) && ('syslog' != $log_file) && !@is_writable($log_file)) - { - $log_error = false; - } - - if ($log_error) - { - @error_log("$note: $message in $file on line $line", 0); - } - } - - return $message; - } - - /** - * If a file has been cached, retrieve and display it. - * - * This is most useful for caching images (get_favicon(), etc.), - * however it works for all cached files. This WILL NOT display ANY - * file/image/page/whatever, but rather only display what has already - * been cached by SimplePie. - * - * @access public - * @see SimplePie::get_favicon() - * @param str $identifier_url URL that is used to identify the content. - * This may or may not be the actual URL of the live content. - * @param str $cache_location Location of SimplePie's cache. Defaults - * to './cache'. - * @param str $cache_extension The file extension that the file was - * cached with. Defaults to 'spc'. - * @param str $cache_class Name of the cache-handling class being used - * in SimplePie. Defaults to 'SimplePie_Cache', and should be left - * as-is unless you've overloaded the class. - * @param str $cache_name_function Obsolete. Exists for backwards - * compatibility reasons only. - */ - function display_cached_file($identifier_url, $cache_location = './cache', $cache_extension = 'spc', $cache_class = 'SimplePie_Cache', $cache_name_function = 'md5') - { - $cache = call_user_func(array($cache_class, 'create'), $cache_location, $identifier_url, $cache_extension); - - if ($file = $cache->load()) - { - if (isset($file['headers']['content-type'])) - { - header('Content-type:' . $file['headers']['content-type']); - } - else - { - header('Content-type: application/octet-stream'); - } - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days - echo $file['body']; - exit; - } - - die('Cached file for ' . $identifier_url . ' cannot be found.'); - } - - function fix_protocol($url, $http = 1) - { - $url = SimplePie_Misc::normalize_url($url); - $parsed = SimplePie_Misc::parse_url($url); - if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') - { - return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http); - } - - if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url)) - { - return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http); - } - - if ($http === 2 && $parsed['scheme'] !== '') - { - return "feed:$url"; - } - elseif ($http === 3 && strtolower($parsed['scheme']) === 'http') - { - return substr_replace($url, 'podcast', 0, 4); - } - elseif ($http === 4 && strtolower($parsed['scheme']) === 'http') - { - return substr_replace($url, 'itpc', 0, 4); - } - else - { - return $url; - } - } - - function parse_url($url) - { - $iri = new SimplePie_IRI($url); - return array( - 'scheme' => (string) $iri->get_scheme(), - 'authority' => (string) $iri->get_authority(), - 'path' => (string) $iri->get_path(), - 'query' => (string) $iri->get_query(), - 'fragment' => (string) $iri->get_fragment() - ); - } - - function compress_parse_url($scheme = '', $authority = '', $path = '', $query = '', $fragment = '') - { - $iri = new SimplePie_IRI(''); - $iri->set_scheme($scheme); - $iri->set_authority($authority); - $iri->set_path($path); - $iri->set_query($query); - $iri->set_fragment($fragment); - return $iri->get_iri(); - } - - function normalize_url($url) - { - $iri = new SimplePie_IRI($url); - return $iri->get_iri(); - } - - function percent_encoding_normalization($match) - { - $integer = hexdec($match[1]); - if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E) - { - return chr($integer); - } - else - { - return strtoupper($match[0]); - } - } - - /** - * Remove bad UTF-8 bytes - * - * PCRE Pattern to locate bad bytes in a UTF-8 string comes from W3C - * FAQ: Multilingual Forms (modified to include full ASCII range) - * - * @author Geoffrey Sneddon - * @see http://www.w3.org/International/questions/qa-forms-utf-8 - * @param string $str String to remove bad UTF-8 bytes from - * @return string UTF-8 string - */ - function utf8_bad_replace($str) - { - if (function_exists('iconv') && ($return = @iconv('UTF-8', 'UTF-8//IGNORE', $str))) - { - return $return; - } - elseif (function_exists('mb_convert_encoding') && ($return = @mb_convert_encoding($str, 'UTF-8', 'UTF-8'))) - { - return $return; - } - elseif (preg_match_all('/(?:[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})+/', $str, $matches)) - { - return implode("\xEF\xBF\xBD", $matches[0]); - } - elseif ($str !== '') - { - return "\xEF\xBF\xBD"; - } - else - { - return ''; - } - } - - /** - * Converts a Windows-1252 encoded string to a UTF-8 encoded string - * - * @static - * @access public - * @param string $string Windows-1252 encoded string - * @return string UTF-8 encoded string - */ - function windows_1252_to_utf8($string) - { - static $convert_table = array("\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF"); - - return strtr($string, $convert_table); - } - - function change_encoding($data, $input, $output) - { - $input = SimplePie_Misc::encoding($input); - $output = SimplePie_Misc::encoding($output); - - // We fail to fail on non US-ASCII bytes - if ($input === 'US-ASCII') - { - static $non_ascii_octects = ''; - if (!$non_ascii_octects) - { - for ($i = 0x80; $i <= 0xFF; $i++) - { - $non_ascii_octects .= chr($i); - } - } - $data = substr($data, 0, strcspn($data, $non_ascii_octects)); - } - - // This is first, as behaviour of this is completely predictable - if ($input === 'Windows-1252' && $output === 'UTF-8') - { - return SimplePie_Misc::windows_1252_to_utf8($data); - } - // This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported). - elseif (function_exists('mb_convert_encoding') && @mb_convert_encoding("\x80", 'UTF-16BE', $input) !== "\x00\x80" && ($return = @mb_convert_encoding($data, $output, $input))) - { - return $return; - } - // This is last, as behaviour of this varies with OS userland and PHP version - elseif (function_exists('iconv') && ($return = @iconv($input, $output, $data))) - { - return $return; - } - // If we can't do anything, just fail - else - { - return false; - } - } - - function encoding($charset) - { - // Normalization from UTS #22 - switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset))) - { - case 'adobestandardencoding': - case 'csadobestandardencoding': - return 'Adobe-Standard-Encoding'; - - case 'adobesymbolencoding': - case 'cshppsmath': - return 'Adobe-Symbol-Encoding'; - - case 'ami1251': - case 'amiga1251': - return 'Amiga-1251'; - - case 'ansix31101983': - case 'csat5001983': - case 'csiso99naplps': - case 'isoir99': - case 'naplps': - return 'ANSI_X3.110-1983'; - - case 'arabic7': - case 'asmo449': - case 'csiso89asmo449': - case 'iso9036': - case 'isoir89': - return 'ASMO_449'; - - case 'big5': - case 'csbig5': - case 'xxbig5': - return 'Big5'; - - case 'big5hkscs': - return 'Big5-HKSCS'; - - case 'bocu1': - case 'csbocu1': - return 'BOCU-1'; - - case 'brf': - case 'csbrf': - return 'BRF'; - - case 'bs4730': - case 'csiso4unitedkingdom': - case 'gb': - case 'iso646gb': - case 'isoir4': - case 'uk': - return 'BS_4730'; - - case 'bsviewdata': - case 'csiso47bsviewdata': - case 'isoir47': - return 'BS_viewdata'; - - case 'cesu8': - case 'cscesu8': - return 'CESU-8'; - - case 'ca': - case 'csa71': - case 'csaz243419851': - case 'csiso121canadian1': - case 'iso646ca': - case 'isoir121': - return 'CSA_Z243.4-1985-1'; - - case 'csa72': - case 'csaz243419852': - case 'csiso122canadian2': - case 'iso646ca2': - case 'isoir122': - return 'CSA_Z243.4-1985-2'; - - case 'csaz24341985gr': - case 'csiso123csaz24341985gr': - case 'isoir123': - return 'CSA_Z243.4-1985-gr'; - - case 'csiso139csn369103': - case 'csn369103': - case 'isoir139': - return 'CSN_369103'; - - case 'csdecmcs': - case 'dec': - case 'decmcs': - return 'DEC-MCS'; - - case 'csiso21german': - case 'de': - case 'din66003': - case 'iso646de': - case 'isoir21': - return 'DIN_66003'; - - case 'csdkus': - case 'dkus': - return 'dk-us'; - - case 'csiso646danish': - case 'dk': - case 'ds2089': - case 'iso646dk': - return 'DS_2089'; - - case 'csibmebcdicatde': - case 'ebcdicatde': - return 'EBCDIC-AT-DE'; - - case 'csebcdicatdea': - case 'ebcdicatdea': - return 'EBCDIC-AT-DE-A'; - - case 'csebcdiccafr': - case 'ebcdiccafr': - return 'EBCDIC-CA-FR'; - - case 'csebcdicdkno': - case 'ebcdicdkno': - return 'EBCDIC-DK-NO'; - - case 'csebcdicdknoa': - case 'ebcdicdknoa': - return 'EBCDIC-DK-NO-A'; - - case 'csebcdices': - case 'ebcdices': - return 'EBCDIC-ES'; - - case 'csebcdicesa': - case 'ebcdicesa': - return 'EBCDIC-ES-A'; - - case 'csebcdicess': - case 'ebcdicess': - return 'EBCDIC-ES-S'; - - case 'csebcdicfise': - case 'ebcdicfise': - return 'EBCDIC-FI-SE'; - - case 'csebcdicfisea': - case 'ebcdicfisea': - return 'EBCDIC-FI-SE-A'; - - case 'csebcdicfr': - case 'ebcdicfr': - return 'EBCDIC-FR'; - - case 'csebcdicit': - case 'ebcdicit': - return 'EBCDIC-IT'; - - case 'csebcdicpt': - case 'ebcdicpt': - return 'EBCDIC-PT'; - - case 'csebcdicuk': - case 'ebcdicuk': - return 'EBCDIC-UK'; - - case 'csebcdicus': - case 'ebcdicus': - return 'EBCDIC-US'; - - case 'csiso111ecmacyrillic': - case 'ecmacyrillic': - case 'isoir111': - case 'koi8e': - return 'ECMA-cyrillic'; - - case 'csiso17spanish': - case 'es': - case 'iso646es': - case 'isoir17': - return 'ES'; - - case 'csiso85spanish2': - case 'es2': - case 'iso646es2': - case 'isoir85': - return 'ES2'; - - case 'cseucfixwidjapanese': - case 'extendedunixcodefixedwidthforjapanese': - return 'Extended_UNIX_Code_Fixed_Width_for_Japanese'; - - case 'cseucpkdfmtjapanese': - case 'eucjp': - case 'extendedunixcodepackedformatforjapanese': - return 'Extended_UNIX_Code_Packed_Format_for_Japanese'; - - case 'gb18030': - return 'GB18030'; - - case 'chinese': - case 'cp936': - case 'csgb2312': - case 'csiso58gb231280': - case 'gb2312': - case 'gb231280': - case 'gbk': - case 'isoir58': - case 'ms936': - case 'windows936': - return 'GBK'; - - case 'cn': - case 'csiso57gb1988': - case 'gb198880': - case 'iso646cn': - case 'isoir57': - return 'GB_1988-80'; - - case 'csiso153gost1976874': - case 'gost1976874': - case 'isoir153': - case 'stsev35888': - return 'GOST_19768-74'; - - case 'csiso150': - case 'csiso150greekccitt': - case 'greekccitt': - case 'isoir150': - return 'greek-ccitt'; - - case 'csiso88greek7': - case 'greek7': - case 'isoir88': - return 'greek7'; - - case 'csiso18greek7old': - case 'greek7old': - case 'isoir18': - return 'greek7-old'; - - case 'cshpdesktop': - case 'hpdesktop': - return 'HP-DeskTop'; - - case 'cshplegal': - case 'hplegal': - return 'HP-Legal'; - - case 'cshpmath8': - case 'hpmath8': - return 'HP-Math8'; - - case 'cshppifont': - case 'hppifont': - return 'HP-Pi-font'; - - case 'cshproman8': - case 'hproman8': - case 'r8': - case 'roman8': - return 'hp-roman8'; - - case 'hzgb2312': - return 'HZ-GB-2312'; - - case 'csibmsymbols': - case 'ibmsymbols': - return 'IBM-Symbols'; - - case 'csibmthai': - case 'ibmthai': - return 'IBM-Thai'; - - case 'ccsid858': - case 'cp858': - case 'ibm858': - case 'pcmultilingual850euro': - return 'IBM00858'; - - case 'ccsid924': - case 'cp924': - case 'ebcdiclatin9euro': - case 'ibm924': - return 'IBM00924'; - - case 'ccsid1140': - case 'cp1140': - case 'ebcdicus37euro': - case 'ibm1140': - return 'IBM01140'; - - case 'ccsid1141': - case 'cp1141': - case 'ebcdicde273euro': - case 'ibm1141': - return 'IBM01141'; - - case 'ccsid1142': - case 'cp1142': - case 'ebcdicdk277euro': - case 'ebcdicno277euro': - case 'ibm1142': - return 'IBM01142'; - - case 'ccsid1143': - case 'cp1143': - case 'ebcdicfi278euro': - case 'ebcdicse278euro': - case 'ibm1143': - return 'IBM01143'; - - case 'ccsid1144': - case 'cp1144': - case 'ebcdicit280euro': - case 'ibm1144': - return 'IBM01144'; - - case 'ccsid1145': - case 'cp1145': - case 'ebcdices284euro': - case 'ibm1145': - return 'IBM01145'; - - case 'ccsid1146': - case 'cp1146': - case 'ebcdicgb285euro': - case 'ibm1146': - return 'IBM01146'; - - case 'ccsid1147': - case 'cp1147': - case 'ebcdicfr297euro': - case 'ibm1147': - return 'IBM01147'; - - case 'ccsid1148': - case 'cp1148': - case 'ebcdicinternational500euro': - case 'ibm1148': - return 'IBM01148'; - - case 'ccsid1149': - case 'cp1149': - case 'ebcdicis871euro': - case 'ibm1149': - return 'IBM01149'; - - case 'cp37': - case 'csibm37': - case 'ebcdiccpca': - case 'ebcdiccpnl': - case 'ebcdiccpus': - case 'ebcdiccpwt': - case 'ibm37': - return 'IBM037'; - - case 'cp38': - case 'csibm38': - case 'ebcdicint': - case 'ibm38': - return 'IBM038'; - - case 'cp273': - case 'csibm273': - case 'ibm273': - return 'IBM273'; - - case 'cp274': - case 'csibm274': - case 'ebcdicbe': - case 'ibm274': - return 'IBM274'; - - case 'cp275': - case 'csibm275': - case 'ebcdicbr': - case 'ibm275': - return 'IBM275'; - - case 'csibm277': - case 'ebcdiccpdk': - case 'ebcdiccpno': - case 'ibm277': - return 'IBM277'; - - case 'cp278': - case 'csibm278': - case 'ebcdiccpfi': - case 'ebcdiccpse': - case 'ibm278': - return 'IBM278'; - - case 'cp280': - case 'csibm280': - case 'ebcdiccpit': - case 'ibm280': - return 'IBM280'; - - case 'cp281': - case 'csibm281': - case 'ebcdicjpe': - case 'ibm281': - return 'IBM281'; - - case 'cp284': - case 'csibm284': - case 'ebcdiccpes': - case 'ibm284': - return 'IBM284'; - - case 'cp285': - case 'csibm285': - case 'ebcdiccpgb': - case 'ibm285': - return 'IBM285'; - - case 'cp290': - case 'csibm290': - case 'ebcdicjpkana': - case 'ibm290': - return 'IBM290'; - - case 'cp297': - case 'csibm297': - case 'ebcdiccpfr': - case 'ibm297': - return 'IBM297'; - - case 'cp420': - case 'csibm420': - case 'ebcdiccpar1': - case 'ibm420': - return 'IBM420'; - - case 'cp423': - case 'csibm423': - case 'ebcdiccpgr': - case 'ibm423': - return 'IBM423'; - - case 'cp424': - case 'csibm424': - case 'ebcdiccphe': - case 'ibm424': - return 'IBM424'; - - case '437': - case 'cp437': - case 'cspc8codepage437': - case 'ibm437': - return 'IBM437'; - - case 'cp500': - case 'csibm500': - case 'ebcdiccpbe': - case 'ebcdiccpch': - case 'ibm500': - return 'IBM500'; - - case 'cp775': - case 'cspc775baltic': - case 'ibm775': - return 'IBM775'; - - case '850': - case 'cp850': - case 'cspc850multilingual': - case 'ibm850': - return 'IBM850'; - - case '851': - case 'cp851': - case 'csibm851': - case 'ibm851': - return 'IBM851'; - - case '852': - case 'cp852': - case 'cspcp852': - case 'ibm852': - return 'IBM852'; - - case '855': - case 'cp855': - case 'csibm855': - case 'ibm855': - return 'IBM855'; - - case '857': - case 'cp857': - case 'csibm857': - case 'ibm857': - return 'IBM857'; - - case '860': - case 'cp860': - case 'csibm860': - case 'ibm860': - return 'IBM860'; - - case '861': - case 'cp861': - case 'cpis': - case 'csibm861': - case 'ibm861': - return 'IBM861'; - - case '862': - case 'cp862': - case 'cspc862latinhebrew': - case 'ibm862': - return 'IBM862'; - - case '863': - case 'cp863': - case 'csibm863': - case 'ibm863': - return 'IBM863'; - - case 'cp864': - case 'csibm864': - case 'ibm864': - return 'IBM864'; - - case '865': - case 'cp865': - case 'csibm865': - case 'ibm865': - return 'IBM865'; - - case '866': - case 'cp866': - case 'csibm866': - case 'ibm866': - return 'IBM866'; - - case 'cp868': - case 'cpar': - case 'csibm868': - case 'ibm868': - return 'IBM868'; - - case '869': - case 'cp869': - case 'cpgr': - case 'csibm869': - case 'ibm869': - return 'IBM869'; - - case 'cp870': - case 'csibm870': - case 'ebcdiccproece': - case 'ebcdiccpyu': - case 'ibm870': - return 'IBM870'; - - case 'cp871': - case 'csibm871': - case 'ebcdiccpis': - case 'ibm871': - return 'IBM871'; - - case 'cp880': - case 'csibm880': - case 'ebcdiccyrillic': - case 'ibm880': - return 'IBM880'; - - case 'cp891': - case 'csibm891': - case 'ibm891': - return 'IBM891'; - - case 'cp903': - case 'csibm903': - case 'ibm903': - return 'IBM903'; - - case '904': - case 'cp904': - case 'csibbm904': - case 'ibm904': - return 'IBM904'; - - case 'cp905': - case 'csibm905': - case 'ebcdiccptr': - case 'ibm905': - return 'IBM905'; - - case 'cp918': - case 'csibm918': - case 'ebcdiccpar2': - case 'ibm918': - return 'IBM918'; - - case 'cp1026': - case 'csibm1026': - case 'ibm1026': - return 'IBM1026'; - - case 'ibm1047': - return 'IBM1047'; - - case 'csiso143iecp271': - case 'iecp271': - case 'isoir143': - return 'IEC_P27-1'; - - case 'csiso49inis': - case 'inis': - case 'isoir49': - return 'INIS'; - - case 'csiso50inis8': - case 'inis8': - case 'isoir50': - return 'INIS-8'; - - case 'csiso51iniscyrillic': - case 'iniscyrillic': - case 'isoir51': - return 'INIS-cyrillic'; - - case 'csinvariant': - case 'invariant': - return 'INVARIANT'; - - case 'iso2022cn': - return 'ISO-2022-CN'; - - case 'iso2022cnext': - return 'ISO-2022-CN-EXT'; - - case 'csiso2022jp': - case 'iso2022jp': - return 'ISO-2022-JP'; - - case 'csiso2022jp2': - case 'iso2022jp2': - return 'ISO-2022-JP-2'; - - case 'csiso2022kr': - case 'iso2022kr': - return 'ISO-2022-KR'; - - case 'cswindows30latin1': - case 'iso88591windows30latin1': - return 'ISO-8859-1-Windows-3.0-Latin-1'; - - case 'cswindows31latin1': - case 'iso88591windows31latin1': - return 'ISO-8859-1-Windows-3.1-Latin-1'; - - case 'csisolatin2': - case 'iso88592': - case 'iso885921987': - case 'isoir101': - case 'l2': - case 'latin2': - return 'ISO-8859-2'; - - case 'cswindows31latin2': - case 'iso88592windowslatin2': - return 'ISO-8859-2-Windows-Latin-2'; - - case 'csisolatin3': - case 'iso88593': - case 'iso885931988': - case 'isoir109': - case 'l3': - case 'latin3': - return 'ISO-8859-3'; - - case 'csisolatin4': - case 'iso88594': - case 'iso885941988': - case 'isoir110': - case 'l4': - case 'latin4': - return 'ISO-8859-4'; - - case 'csisolatincyrillic': - case 'cyrillic': - case 'iso88595': - case 'iso885951988': - case 'isoir144': - return 'ISO-8859-5'; - - case 'arabic': - case 'asmo708': - case 'csisolatinarabic': - case 'ecma114': - case 'iso88596': - case 'iso885961987': - case 'isoir127': - return 'ISO-8859-6'; - - case 'csiso88596e': - case 'iso88596e': - return 'ISO-8859-6-E'; - - case 'csiso88596i': - case 'iso88596i': - return 'ISO-8859-6-I'; - - case 'csisolatingreek': - case 'ecma118': - case 'elot928': - case 'greek': - case 'greek8': - case 'iso88597': - case 'iso885971987': - case 'isoir126': - return 'ISO-8859-7'; - - case 'csisolatinhebrew': - case 'hebrew': - case 'iso88598': - case 'iso885981988': - case 'isoir138': - return 'ISO-8859-8'; - - case 'csiso88598e': - case 'iso88598e': - return 'ISO-8859-8-E'; - - case 'csiso88598i': - case 'iso88598i': - return 'ISO-8859-8-I'; - - case 'cswindows31latin5': - case 'iso88599windowslatin5': - return 'ISO-8859-9-Windows-Latin-5'; - - case 'csisolatin6': - case 'iso885910': - case 'iso8859101992': - case 'isoir157': - case 'l6': - case 'latin6': - return 'ISO-8859-10'; - - case 'iso885913': - return 'ISO-8859-13'; - - case 'iso885914': - case 'iso8859141998': - case 'isoceltic': - case 'isoir199': - case 'l8': - case 'latin8': - return 'ISO-8859-14'; - - case 'iso885915': - case 'latin9': - return 'ISO-8859-15'; - - case 'iso885916': - case 'iso8859162001': - case 'isoir226': - case 'l10': - case 'latin10': - return 'ISO-8859-16'; - - case 'iso10646j1': - return 'ISO-10646-J-1'; - - case 'csunicode': - case 'iso10646ucs2': - return 'ISO-10646-UCS-2'; - - case 'csucs4': - case 'iso10646ucs4': - return 'ISO-10646-UCS-4'; - - case 'csunicodeascii': - case 'iso10646ucsbasic': - return 'ISO-10646-UCS-Basic'; - - case 'csunicodelatin1': - case 'iso10646': - case 'iso10646unicodelatin1': - return 'ISO-10646-Unicode-Latin1'; - - case 'csiso10646utf1': - case 'iso10646utf1': - return 'ISO-10646-UTF-1'; - - case 'csiso115481': - case 'iso115481': - case 'isotr115481': - return 'ISO-11548-1'; - - case 'csiso90': - case 'isoir90': - return 'iso-ir-90'; - - case 'csunicodeibm1261': - case 'isounicodeibm1261': - return 'ISO-Unicode-IBM-1261'; - - case 'csunicodeibm1264': - case 'isounicodeibm1264': - return 'ISO-Unicode-IBM-1264'; - - case 'csunicodeibm1265': - case 'isounicodeibm1265': - return 'ISO-Unicode-IBM-1265'; - - case 'csunicodeibm1268': - case 'isounicodeibm1268': - return 'ISO-Unicode-IBM-1268'; - - case 'csunicodeibm1276': - case 'isounicodeibm1276': - return 'ISO-Unicode-IBM-1276'; - - case 'csiso646basic1983': - case 'iso646basic1983': - case 'ref': - return 'ISO_646.basic:1983'; - - case 'csiso2intlrefversion': - case 'irv': - case 'iso646irv1983': - case 'isoir2': - return 'ISO_646.irv:1983'; - - case 'csiso2033': - case 'e13b': - case 'iso20331983': - case 'isoir98': - return 'ISO_2033-1983'; - - case 'csiso5427cyrillic': - case 'iso5427': - case 'isoir37': - return 'ISO_5427'; - - case 'iso5427cyrillic1981': - case 'iso54271981': - case 'isoir54': - return 'ISO_5427:1981'; - - case 'csiso5428greek': - case 'iso54281980': - case 'isoir55': - return 'ISO_5428:1980'; - - case 'csiso6937add': - case 'iso6937225': - case 'isoir152': - return 'ISO_6937-2-25'; - - case 'csisotextcomm': - case 'iso69372add': - case 'isoir142': - return 'ISO_6937-2-add'; - - case 'csiso8859supp': - case 'iso8859supp': - case 'isoir154': - case 'latin125': - return 'ISO_8859-supp'; - - case 'csiso10367box': - case 'iso10367box': - case 'isoir155': - return 'ISO_10367-box'; - - case 'csiso15italian': - case 'iso646it': - case 'isoir15': - case 'it': - return 'IT'; - - case 'csiso13jisc6220jp': - case 'isoir13': - case 'jisc62201969': - case 'jisc62201969jp': - case 'katakana': - case 'x2017': - return 'JIS_C6220-1969-jp'; - - case 'csiso14jisc6220ro': - case 'iso646jp': - case 'isoir14': - case 'jisc62201969ro': - case 'jp': - return 'JIS_C6220-1969-ro'; - - case 'csiso42jisc62261978': - case 'isoir42': - case 'jisc62261978': - return 'JIS_C6226-1978'; - - case 'csiso87jisx208': - case 'isoir87': - case 'jisc62261983': - case 'jisx2081983': - case 'x208': - return 'JIS_C6226-1983'; - - case 'csiso91jisc62291984a': - case 'isoir91': - case 'jisc62291984a': - case 'jpocra': - return 'JIS_C6229-1984-a'; - - case 'csiso92jisc62991984b': - case 'iso646jpocrb': - case 'isoir92': - case 'jisc62291984b': - case 'jpocrb': - return 'JIS_C6229-1984-b'; - - case 'csiso93jis62291984badd': - case 'isoir93': - case 'jisc62291984badd': - case 'jpocrbadd': - return 'JIS_C6229-1984-b-add'; - - case 'csiso94jis62291984hand': - case 'isoir94': - case 'jisc62291984hand': - case 'jpocrhand': - return 'JIS_C6229-1984-hand'; - - case 'csiso95jis62291984handadd': - case 'isoir95': - case 'jisc62291984handadd': - case 'jpocrhandadd': - return 'JIS_C6229-1984-hand-add'; - - case 'csiso96jisc62291984kana': - case 'isoir96': - case 'jisc62291984kana': - return 'JIS_C6229-1984-kana'; - - case 'csjisencoding': - case 'jisencoding': - return 'JIS_Encoding'; - - case 'cshalfwidthkatakana': - case 'jisx201': - case 'x201': - return 'JIS_X0201'; - - case 'csiso159jisx2121990': - case 'isoir159': - case 'jisx2121990': - case 'x212': - return 'JIS_X0212-1990'; - - case 'csiso141jusib1002': - case 'iso646yu': - case 'isoir141': - case 'js': - case 'jusib1002': - case 'yu': - return 'JUS_I.B1.002'; - - case 'csiso147macedonian': - case 'isoir147': - case 'jusib1003mac': - case 'macedonian': - return 'JUS_I.B1.003-mac'; - - case 'csiso146serbian': - case 'isoir146': - case 'jusib1003serb': - case 'serbian': - return 'JUS_I.B1.003-serb'; - - case 'koi7switched': - return 'KOI7-switched'; - - case 'cskoi8r': - case 'koi8r': - return 'KOI8-R'; - - case 'koi8u': - return 'KOI8-U'; - - case 'csksc5636': - case 'iso646kr': - case 'ksc5636': - return 'KSC5636'; - - case 'cskz1048': - case 'kz1048': - case 'rk1048': - case 'strk10482002': - return 'KZ-1048'; - - case 'csiso19latingreek': - case 'isoir19': - case 'latingreek': - return 'latin-greek'; - - case 'csiso27latingreek1': - case 'isoir27': - case 'latingreek1': - return 'Latin-greek-1'; - - case 'csiso158lap': - case 'isoir158': - case 'lap': - case 'latinlap': - return 'latin-lap'; - - case 'csmacintosh': - case 'mac': - case 'macintosh': - return 'macintosh'; - - case 'csmicrosoftpublishing': - case 'microsoftpublishing': - return 'Microsoft-Publishing'; - - case 'csmnem': - case 'mnem': - return 'MNEM'; - - case 'csmnemonic': - case 'mnemonic': - return 'MNEMONIC'; - - case 'csiso86hungarian': - case 'hu': - case 'iso646hu': - case 'isoir86': - case 'msz77953': - return 'MSZ_7795.3'; - - case 'csnatsdano': - case 'isoir91': - case 'natsdano': - return 'NATS-DANO'; - - case 'csnatsdanoadd': - case 'isoir92': - case 'natsdanoadd': - return 'NATS-DANO-ADD'; - - case 'csnatssefi': - case 'isoir81': - case 'natssefi': - return 'NATS-SEFI'; - - case 'csnatssefiadd': - case 'isoir82': - case 'natssefiadd': - return 'NATS-SEFI-ADD'; - - case 'csiso151cuba': - case 'cuba': - case 'iso646cu': - case 'isoir151': - case 'ncnc1081': - return 'NC_NC00-10:81'; - - case 'csiso69french': - case 'fr': - case 'iso646fr': - case 'isoir69': - case 'nfz62010': - return 'NF_Z_62-010'; - - case 'csiso25french': - case 'iso646fr1': - case 'isoir25': - case 'nfz620101973': - return 'NF_Z_62-010_(1973)'; - - case 'csiso60danishnorwegian': - case 'csiso60norwegian1': - case 'iso646no': - case 'isoir60': - case 'no': - case 'ns45511': - return 'NS_4551-1'; - - case 'csiso61norwegian2': - case 'iso646no2': - case 'isoir61': - case 'no2': - case 'ns45512': - return 'NS_4551-2'; - - case 'osdebcdicdf3irv': - return 'OSD_EBCDIC_DF03_IRV'; - - case 'osdebcdicdf41': - return 'OSD_EBCDIC_DF04_1'; - - case 'osdebcdicdf415': - return 'OSD_EBCDIC_DF04_15'; - - case 'cspc8danishnorwegian': - case 'pc8danishnorwegian': - return 'PC8-Danish-Norwegian'; - - case 'cspc8turkish': - case 'pc8turkish': - return 'PC8-Turkish'; - - case 'csiso16portuguese': - case 'iso646pt': - case 'isoir16': - case 'pt': - return 'PT'; - - case 'csiso84portuguese2': - case 'iso646pt2': - case 'isoir84': - case 'pt2': - return 'PT2'; - - case 'cp154': - case 'csptcp154': - case 'cyrillicasian': - case 'pt154': - case 'ptcp154': - return 'PTCP154'; - - case 'scsu': - return 'SCSU'; - - case 'csiso10swedish': - case 'fi': - case 'iso646fi': - case 'iso646se': - case 'isoir10': - case 'se': - case 'sen850200b': - return 'SEN_850200_B'; - - case 'csiso11swedishfornames': - case 'iso646se2': - case 'isoir11': - case 'se2': - case 'sen850200c': - return 'SEN_850200_C'; - - case 'csshiftjis': - case 'mskanji': - case 'shiftjis': - return 'Shift_JIS'; - - case 'csiso102t617bit': - case 'isoir102': - case 't617bit': - return 'T.61-7bit'; - - case 'csiso103t618bit': - case 'isoir103': - case 't61': - case 't618bit': - return 'T.61-8bit'; - - case 'csiso128t101g2': - case 'isoir128': - case 't101g2': - return 'T.101-G2'; - - case 'cstscii': - case 'tscii': - return 'TSCII'; - - case 'csunicode11': - case 'unicode11': - return 'UNICODE-1-1'; - - case 'csunicode11utf7': - case 'unicode11utf7': - return 'UNICODE-1-1-UTF-7'; - - case 'csunknown8bit': - case 'unknown8bit': - return 'UNKNOWN-8BIT'; - - case 'ansix341968': - case 'ansix341986': - case 'ascii': - case 'cp367': - case 'csascii': - case 'ibm367': - case 'iso646irv1991': - case 'iso646us': - case 'isoir6': - case 'us': - case 'usascii': - return 'US-ASCII'; - - case 'csusdk': - case 'usdk': - return 'us-dk'; - - case 'utf7': - return 'UTF-7'; - - case 'utf8': - return 'UTF-8'; - - case 'utf16': - return 'UTF-16'; - - case 'utf16be': - return 'UTF-16BE'; - - case 'utf16le': - return 'UTF-16LE'; - - case 'utf32': - return 'UTF-32'; - - case 'utf32be': - return 'UTF-32BE'; - - case 'utf32le': - return 'UTF-32LE'; - - case 'csventurainternational': - case 'venturainternational': - return 'Ventura-International'; - - case 'csventuramath': - case 'venturamath': - return 'Ventura-Math'; - - case 'csventuraus': - case 'venturaus': - return 'Ventura-US'; - - case 'csiso70videotexsupp1': - case 'isoir70': - case 'videotexsuppl': - return 'videotex-suppl'; - - case 'csviqr': - case 'viqr': - return 'VIQR'; - - case 'csviscii': - case 'viscii': - return 'VISCII'; - - case 'cswindows31j': - case 'windows31j': - return 'Windows-31J'; - - case 'iso885911': - case 'tis620': - return 'windows-874'; - - case 'cseuckr': - case 'csksc56011987': - case 'euckr': - case 'isoir149': - case 'korean': - case 'ksc5601': - case 'ksc56011987': - case 'ksc56011989': - case 'windows949': - return 'windows-949'; - - case 'windows1250': - return 'windows-1250'; - - case 'windows1251': - return 'windows-1251'; - - case 'cp819': - case 'csisolatin1': - case 'ibm819': - case 'iso88591': - case 'iso885911987': - case 'isoir100': - case 'l1': - case 'latin1': - case 'windows1252': - return 'windows-1252'; - - case 'windows1253': - return 'windows-1253'; - - case 'csisolatin5': - case 'iso88599': - case 'iso885991989': - case 'isoir148': - case 'l5': - case 'latin5': - case 'windows1254': - return 'windows-1254'; - - case 'windows1255': - return 'windows-1255'; - - case 'windows1256': - return 'windows-1256'; - - case 'windows1257': - return 'windows-1257'; - - case 'windows1258': - return 'windows-1258'; - - default: - return $charset; - } - } - - function get_curl_version() - { - if (is_array($curl = curl_version())) - { - $curl = $curl['version']; - } - elseif (substr($curl, 0, 5) === 'curl/') - { - $curl = substr($curl, 5, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 5)); - } - elseif (substr($curl, 0, 8) === 'libcurl/') - { - $curl = substr($curl, 8, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 8)); - } - else - { - $curl = 0; - } - return $curl; - } - - function is_subclass_of($class1, $class2) - { - if (func_num_args() !== 2) - { - trigger_error('Wrong parameter count for SimplePie_Misc::is_subclass_of()', E_USER_WARNING); - } - elseif (version_compare(PHP_VERSION, '5.0.3', '>=') || is_object($class1)) - { - return is_subclass_of($class1, $class2); - } - elseif (is_string($class1) && is_string($class2)) - { - if (class_exists($class1)) - { - if (class_exists($class2)) - { - $class2 = strtolower($class2); - while ($class1 = strtolower(get_parent_class($class1))) - { - if ($class1 === $class2) - { - return true; - } - } - } - } - else - { - trigger_error('Unknown class passed as parameter', E_USER_WARNNG); - } - } - return false; - } - - /** - * Strip HTML comments - * - * @access public - * @param string $data Data to strip comments from - * @return string Comment stripped string - */ - function strip_comments($data) - { - $output = ''; - while (($start = strpos($data, '<!--')) !== false) - { - $output .= substr($data, 0, $start); - if (($end = strpos($data, '-->', $start)) !== false) - { - $data = substr_replace($data, '', 0, $end + 3); - } - else - { - $data = ''; - } - } - return $output . $data; - } - - function parse_date($dt) - { - $parser = SimplePie_Parse_Date::get(); - return $parser->parse($dt); - } - - /** - * Decode HTML entities - * - * @static - * @access public - * @param string $data Input data - * @return string Output data - */ - function entities_decode($data) - { - $decoder = new SimplePie_Decode_HTML_Entities($data); - return $decoder->parse(); - } - - /** - * Remove RFC822 comments - * - * @access public - * @param string $data Data to strip comments from - * @return string Comment stripped string - */ - function uncomment_rfc822($string) - { - $string = (string) $string; - $position = 0; - $length = strlen($string); - $depth = 0; - - $output = ''; - - while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) - { - $output .= substr($string, $position, $pos - $position); - $position = $pos + 1; - if ($string[$pos - 1] !== '\\') - { - $depth++; - while ($depth && $position < $length) - { - $position += strcspn($string, '()', $position); - if ($string[$position - 1] === '\\') - { - $position++; - continue; - } - elseif (isset($string[$position])) - { - switch ($string[$position]) - { - case '(': - $depth++; - break; - - case ')': - $depth--; - break; - } - $position++; - } - else - { - break; - } - } - } - else - { - $output .= '('; - } - } - $output .= substr($string, $position); - - return $output; - } - - function parse_mime($mime) - { - if (($pos = strpos($mime, ';')) === false) - { - return trim($mime); - } - else - { - return trim(substr($mime, 0, $pos)); - } - } - - function htmlspecialchars_decode($string, $quote_style) - { - if (function_exists('htmlspecialchars_decode')) - { - return htmlspecialchars_decode($string, $quote_style); - } - else - { - return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style))); - } - } - - function atom_03_construct_type($attribs) - { - if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode']) === 'base64')) - { - $mode = SIMPLEPIE_CONSTRUCT_BASE64; - } - else - { - $mode = SIMPLEPIE_CONSTRUCT_NONE; - } - if (isset($attribs['']['type'])) - { - switch (strtolower(trim($attribs['']['type']))) - { - case 'text': - case 'text/plain': - return SIMPLEPIE_CONSTRUCT_TEXT | $mode; - - case 'html': - case 'text/html': - return SIMPLEPIE_CONSTRUCT_HTML | $mode; - - case 'xhtml': - case 'application/xhtml+xml': - return SIMPLEPIE_CONSTRUCT_XHTML | $mode; - - default: - return SIMPLEPIE_CONSTRUCT_NONE | $mode; - } - } - else - { - return SIMPLEPIE_CONSTRUCT_TEXT | $mode; - } - } - - function atom_10_construct_type($attribs) - { - if (isset($attribs['']['type'])) - { - switch (strtolower(trim($attribs['']['type']))) - { - case 'text': - return SIMPLEPIE_CONSTRUCT_TEXT; - - case 'html': - return SIMPLEPIE_CONSTRUCT_HTML; - - case 'xhtml': - return SIMPLEPIE_CONSTRUCT_XHTML; - - default: - return SIMPLEPIE_CONSTRUCT_NONE; - } - } - return SIMPLEPIE_CONSTRUCT_TEXT; - } - - function atom_10_content_construct_type($attribs) - { - if (isset($attribs['']['type'])) - { - $type = strtolower(trim($attribs['']['type'])); - switch ($type) - { - case 'text': - return SIMPLEPIE_CONSTRUCT_TEXT; - - case 'html': - return SIMPLEPIE_CONSTRUCT_HTML; - - case 'xhtml': - return SIMPLEPIE_CONSTRUCT_XHTML; - } - if (in_array(substr($type, -4), array('+xml', '/xml')) || substr($type, 0, 5) === 'text/') - { - return SIMPLEPIE_CONSTRUCT_NONE; - } - else - { - return SIMPLEPIE_CONSTRUCT_BASE64; - } - } - else - { - return SIMPLEPIE_CONSTRUCT_TEXT; - } - } - - function is_isegment_nz_nc($string) - { - return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string); - } - - function space_seperated_tokens($string) - { - $space_characters = "\x20\x09\x0A\x0B\x0C\x0D"; - $string_length = strlen($string); - - $position = strspn($string, $space_characters); - $tokens = array(); - - while ($position < $string_length) - { - $len = strcspn($string, $space_characters, $position); - $tokens[] = substr($string, $position, $len); - $position += $len; - $position += strspn($string, $space_characters, $position); - } - - return $tokens; - } - - function array_unique($array) - { - if (version_compare(PHP_VERSION, '5.2', '>=')) - { - return array_unique($array); - } - else - { - $array = (array) $array; - $new_array = array(); - $new_array_strings = array(); - foreach ($array as $key => $value) - { - if (is_object($value)) - { - if (method_exists($value, '__toString')) - { - $cmp = $value->__toString(); - } - else - { - trigger_error('Object of class ' . get_class($value) . ' could not be converted to string', E_USER_ERROR); - } - } - elseif (is_array($value)) - { - $cmp = (string) reset($value); - } - else - { - $cmp = (string) $value; - } - if (!in_array($cmp, $new_array_strings)) - { - $new_array[$key] = $value; - $new_array_strings[] = $cmp; - } - } - return $new_array; - } - } - - /** - * Converts a unicode codepoint to a UTF-8 character - * - * @static - * @access public - * @param int $codepoint Unicode codepoint - * @return string UTF-8 character - */ - function codepoint_to_utf8($codepoint) - { - $codepoint = (int) $codepoint; - if ($codepoint < 0) - { - return false; - } - else if ($codepoint <= 0x7f) - { - return chr($codepoint); - } - else if ($codepoint <= 0x7ff) - { - return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f)); - } - else if ($codepoint <= 0xffff) - { - return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); - } - else if ($codepoint <= 0x10ffff) - { - return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f)); - } - else - { - // U+FFFD REPLACEMENT CHARACTER - return "\xEF\xBF\xBD"; - } - } - - /** - * Re-implementation of PHP 5's stripos() - * - * Returns the numeric position of the first occurrence of needle in the - * haystack string. - * - * @static - * @access string - * @param object $haystack - * @param string $needle Note that the needle may be a string of one or more - * characters. If needle is not a string, it is converted to an integer - * and applied as the ordinal value of a character. - * @param int $offset The optional offset parameter allows you to specify which - * character in haystack to start searching. The position returned is still - * relative to the beginning of haystack. - * @return bool If needle is not found, stripos() will return boolean false. - */ - function stripos($haystack, $needle, $offset = 0) - { - if (function_exists('stripos')) - { - return stripos($haystack, $needle, $offset); - } - else - { - if (is_string($needle)) - { - $needle = strtolower($needle); - } - elseif (is_int($needle) || is_bool($needle) || is_double($needle)) - { - $needle = strtolower(chr($needle)); - } - else - { - trigger_error('needle is not a string or an integer', E_USER_WARNING); - return false; - } - - return strpos(strtolower($haystack), $needle, $offset); - } - } - - /** - * Similar to parse_str() - * - * Returns an associative array of name/value pairs, where the value is an - * array of values that have used the same name - * - * @static - * @access string - * @param string $str The input string. - * @return array - */ - function parse_str($str) - { - $return = array(); - $str = explode('&', $str); - - foreach ($str as $section) - { - if (strpos($section, '=') !== false) - { - list($name, $value) = explode('=', $section, 2); - $return[urldecode($name)][] = urldecode($value); - } - else - { - $return[urldecode($section)][] = null; - } - } - - return $return; - } - - /** - * Detect XML encoding, as per XML 1.0 Appendix F.1 - * - * @todo Add support for EBCDIC - * @param string $data XML data - * @return array Possible encodings - */ - function xml_encoding($data) - { - // UTF-32 Big Endian BOM - if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") - { - $encoding[] = 'UTF-32BE'; - } - // UTF-32 Little Endian BOM - elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") - { - $encoding[] = 'UTF-32LE'; - } - // UTF-16 Big Endian BOM - elseif (substr($data, 0, 2) === "\xFE\xFF") - { - $encoding[] = 'UTF-16BE'; - } - // UTF-16 Little Endian BOM - elseif (substr($data, 0, 2) === "\xFF\xFE") - { - $encoding[] = 'UTF-16LE'; - } - // UTF-8 BOM - elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") - { - $encoding[] = 'UTF-8'; - } - // UTF-32 Big Endian Without BOM - elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C") - { - if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E")) - { - $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8')); - if ($parser->parse()) - { - $encoding[] = $parser->encoding; - } - } - $encoding[] = 'UTF-32BE'; - } - // UTF-32 Little Endian Without BOM - elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00") - { - if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00")) - { - $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8')); - if ($parser->parse()) - { - $encoding[] = $parser->encoding; - } - } - $encoding[] = 'UTF-32LE'; - } - // UTF-16 Big Endian Without BOM - elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C") - { - if ($pos = strpos($data, "\x00\x3F\x00\x3E")) - { - $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8')); - if ($parser->parse()) - { - $encoding[] = $parser->encoding; - } - } - $encoding[] = 'UTF-16BE'; - } - // UTF-16 Little Endian Without BOM - elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00") - { - if ($pos = strpos($data, "\x3F\x00\x3E\x00")) - { - $parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8')); - if ($parser->parse()) - { - $encoding[] = $parser->encoding; - } - } - $encoding[] = 'UTF-16LE'; - } - // US-ASCII (or superset) - elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C") - { - if ($pos = strpos($data, "\x3F\x3E")) - { - $parser = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5)); - if ($parser->parse()) - { - $encoding[] = $parser->encoding; - } - } - $encoding[] = 'UTF-8'; - } - // Fallback to UTF-8 - else - { - $encoding[] = 'UTF-8'; - } - return $encoding; - } - - function output_javascript() - { - if (function_exists('ob_gzhandler')) - { - ob_start('ob_gzhandler'); - } - header('Content-type: text/javascript; charset: UTF-8'); - header('Cache-Control: must-revalidate'); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days - ?> -function embed_odeo(link) { - document.writeln('<embed src="http://odeo.com/flash/audio_player_fullsize.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="440" height="80" wmode="transparent" allowScriptAccess="any" flashvars="valid_sample_rate=true&external_url='+link+'"></embed>'); -} - -function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) { - if (placeholder != '') { - document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" href="'+link+'" src="'+placeholder+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="false" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>'); - } - else { - document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" src="'+link+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="true" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>'); - } -} - -function embed_flash(bgcolor, width, height, link, loop, type) { - document.writeln('<embed src="'+link+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="'+type+'" quality="high" width="'+width+'" height="'+height+'" bgcolor="'+bgcolor+'" loop="'+loop+'"></embed>'); -} - -function embed_flv(width, height, link, placeholder, loop, player) { - document.writeln('<embed src="'+player+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="'+width+'" height="'+height+'" wmode="transparent" flashvars="file='+link+'&autostart=false&repeat='+loop+'&showdigits=true&showfsbutton=false"></embed>'); -} - -function embed_wmedia(width, height, link) { - document.writeln('<embed type="application/x-mplayer2" src="'+link+'" autosize="1" width="'+width+'" height="'+height+'" showcontrols="1" showstatusbar="0" showdisplay="0" autostart="0"></embed>'); -} - <?php - } -} - -/** - * Decode HTML Entities - * - * This implements HTML5 as of revision 967 (2007-06-28) - * - * @package SimplePie - */ -class SimplePie_Decode_HTML_Entities -{ - /** - * Data to be parsed - * - * @access private - * @var string - */ - var $data = ''; - - /** - * Currently consumed bytes - * - * @access private - * @var string - */ - var $consumed = ''; - - /** - * Position of the current byte being parsed - * - * @access private - * @var int - */ - var $position = 0; - - /** - * Create an instance of the class with the input data - * - * @access public - * @param string $data Input data - */ - function SimplePie_Decode_HTML_Entities($data) - { - $this->data = $data; - } - - /** - * Parse the input data - * - * @access public - * @return string Output data - */ - function parse() - { - while (($this->position = strpos($this->data, '&', $this->position)) !== false) - { - $this->consume(); - $this->entity(); - $this->consumed = ''; - } - return $this->data; - } - - /** - * Consume the next byte - * - * @access private - * @return mixed The next byte, or false, if there is no more data - */ - function consume() - { - if (isset($this->data[$this->position])) - { - $this->consumed .= $this->data[$this->position]; - return $this->data[$this->position++]; - } - else - { - return false; - } - } - - /** - * Consume a range of characters - * - * @access private - * @param string $chars Characters to consume - * @return mixed A series of characters that match the range, or false - */ - function consume_range($chars) - { - if ($len = strspn($this->data, $chars, $this->position)) - { - $data = substr($this->data, $this->position, $len); - $this->consumed .= $data; - $this->position += $len; - return $data; - } - else - { - return false; - } - } - - /** - * Unconsume one byte - * - * @access private - */ - function unconsume() - { - $this->consumed = substr($this->consumed, 0, -1); - $this->position--; - } - - /** - * Decode an entity - * - * @access private - */ - function entity() - { - switch ($this->consume()) - { - case "\x09": - case "\x0A": - case "\x0B": - case "\x0B": - case "\x0C": - case "\x20": - case "\x3C": - case "\x26": - case false: - break; - - case "\x23": - switch ($this->consume()) - { - case "\x78": - case "\x58": - $range = '0123456789ABCDEFabcdef'; - $hex = true; - break; - - default: - $range = '0123456789'; - $hex = false; - $this->unconsume(); - break; - } - - if ($codepoint = $this->consume_range($range)) - { - static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8"); - - if ($hex) - { - $codepoint = hexdec($codepoint); - } - else - { - $codepoint = intval($codepoint); - } - - if (isset($windows_1252_specials[$codepoint])) - { - $replacement = $windows_1252_specials[$codepoint]; - } - else - { - $replacement = SimplePie_Misc::codepoint_to_utf8($codepoint); - } - - if (!in_array($this->consume(), array(';', false), true)) - { - $this->unconsume(); - } - - $consumed_length = strlen($this->consumed); - $this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length); - $this->position += strlen($replacement) - $consumed_length; - } - break; - - default: - static $entities = array('Aacute' => "\xC3\x81", 'aacute' => "\xC3\xA1", 'Aacute;' => "\xC3\x81", 'aacute;' => "\xC3\xA1", 'Acirc' => "\xC3\x82", 'acirc' => "\xC3\xA2", 'Acirc;' => "\xC3\x82", 'acirc;' => "\xC3\xA2", 'acute' => "\xC2\xB4", 'acute;' => "\xC2\xB4", 'AElig' => "\xC3\x86", 'aelig' => "\xC3\xA6", 'AElig;' => "\xC3\x86", 'aelig;' => "\xC3\xA6", 'Agrave' => "\xC3\x80", 'agrave' => "\xC3\xA0", 'Agrave;' => "\xC3\x80", 'agrave;' => "\xC3\xA0", 'alefsym;' => "\xE2\x84\xB5", 'Alpha;' => "\xCE\x91", 'alpha;' => "\xCE\xB1", 'AMP' => "\x26", 'amp' => "\x26", 'AMP;' => "\x26", 'amp;' => "\x26", 'and;' => "\xE2\x88\xA7", 'ang;' => "\xE2\x88\xA0", 'apos;' => "\x27", 'Aring' => "\xC3\x85", 'aring' => "\xC3\xA5", 'Aring;' => "\xC3\x85", 'aring;' => "\xC3\xA5", 'asymp;' => "\xE2\x89\x88", 'Atilde' => "\xC3\x83", 'atilde' => "\xC3\xA3", 'Atilde;' => "\xC3\x83", 'atilde;' => "\xC3\xA3", 'Auml' => "\xC3\x84", 'auml' => "\xC3\xA4", 'Auml;' => "\xC3\x84", 'auml;' => "\xC3\xA4", 'bdquo;' => "\xE2\x80\x9E", 'Beta;' => "\xCE\x92", 'beta;' => "\xCE\xB2", 'brvbar' => "\xC2\xA6", 'brvbar;' => "\xC2\xA6", 'bull;' => "\xE2\x80\xA2", 'cap;' => "\xE2\x88\xA9", 'Ccedil' => "\xC3\x87", 'ccedil' => "\xC3\xA7", 'Ccedil;' => "\xC3\x87", 'ccedil;' => "\xC3\xA7", 'cedil' => "\xC2\xB8", 'cedil;' => "\xC2\xB8", 'cent' => "\xC2\xA2", 'cent;' => "\xC2\xA2", 'Chi;' => "\xCE\xA7", 'chi;' => "\xCF\x87", 'circ;' => "\xCB\x86", 'clubs;' => "\xE2\x99\xA3", 'cong;' => "\xE2\x89\x85", 'COPY' => "\xC2\xA9", 'copy' => "\xC2\xA9", 'COPY;' => "\xC2\xA9", 'copy;' => "\xC2\xA9", 'crarr;' => "\xE2\x86\xB5", 'cup;' => "\xE2\x88\xAA", 'curren' => "\xC2\xA4", 'curren;' => "\xC2\xA4", 'Dagger;' => "\xE2\x80\xA1", 'dagger;' => "\xE2\x80\xA0", 'dArr;' => "\xE2\x87\x93", 'darr;' => "\xE2\x86\x93", 'deg' => "\xC2\xB0", 'deg;' => "\xC2\xB0", 'Delta;' => "\xCE\x94", 'delta;' => "\xCE\xB4", 'diams;' => "\xE2\x99\xA6", 'divide' => "\xC3\xB7", 'divide;' => "\xC3\xB7", 'Eacute' => "\xC3\x89", 'eacute' => "\xC3\xA9", 'Eacute;' => "\xC3\x89", 'eacute;' => "\xC3\xA9", 'Ecirc' => "\xC3\x8A", 'ecirc' => "\xC3\xAA", 'Ecirc;' => "\xC3\x8A", 'ecirc;' => "\xC3\xAA", 'Egrave' => "\xC3\x88", 'egrave' => "\xC3\xA8", 'Egrave;' => "\xC3\x88", 'egrave;' => "\xC3\xA8", 'empty;' => "\xE2\x88\x85", 'emsp;' => "\xE2\x80\x83", 'ensp;' => "\xE2\x80\x82", 'Epsilon;' => "\xCE\x95", 'epsilon;' => "\xCE\xB5", 'equiv;' => "\xE2\x89\xA1", 'Eta;' => "\xCE\x97", 'eta;' => "\xCE\xB7", 'ETH' => "\xC3\x90", 'eth' => "\xC3\xB0", 'ETH;' => "\xC3\x90", 'eth;' => "\xC3\xB0", 'Euml' => "\xC3\x8B", 'euml' => "\xC3\xAB", 'Euml;' => "\xC3\x8B", 'euml;' => "\xC3\xAB", 'euro;' => "\xE2\x82\xAC", 'exist;' => "\xE2\x88\x83", 'fnof;' => "\xC6\x92", 'forall;' => "\xE2\x88\x80", 'frac12' => "\xC2\xBD", 'frac12;' => "\xC2\xBD", 'frac14' => "\xC2\xBC", 'frac14;' => "\xC2\xBC", 'frac34' => "\xC2\xBE", 'frac34;' => "\xC2\xBE", 'frasl;' => "\xE2\x81\x84", 'Gamma;' => "\xCE\x93", 'gamma;' => "\xCE\xB3", 'ge;' => "\xE2\x89\xA5", 'GT' => "\x3E", 'gt' => "\x3E", 'GT;' => "\x3E", 'gt;' => "\x3E", 'hArr;' => "\xE2\x87\x94", 'harr;' => "\xE2\x86\x94", 'hearts;' => "\xE2\x99\xA5", 'hellip;' => "\xE2\x80\xA6", 'Iacute' => "\xC3\x8D", 'iacute' => "\xC3\xAD", 'Iacute;' => "\xC3\x8D", 'iacute;' => "\xC3\xAD", 'Icirc' => "\xC3\x8E", 'icirc' => "\xC3\xAE", 'Icirc;' => "\xC3\x8E", 'icirc;' => "\xC3\xAE", 'iexcl' => "\xC2\xA1", 'iexcl;' => "\xC2\xA1", 'Igrave' => "\xC3\x8C", 'igrave' => "\xC3\xAC", 'Igrave;' => "\xC3\x8C", 'igrave;' => "\xC3\xAC", 'image;' => "\xE2\x84\x91", 'infin;' => "\xE2\x88\x9E", 'int;' => "\xE2\x88\xAB", 'Iota;' => "\xCE\x99", 'iota;' => "\xCE\xB9", 'iquest' => "\xC2\xBF", 'iquest;' => "\xC2\xBF", 'isin;' => "\xE2\x88\x88", 'Iuml' => "\xC3\x8F", 'iuml' => "\xC3\xAF", 'Iuml;' => "\xC3\x8F", 'iuml;' => "\xC3\xAF", 'Kappa;' => "\xCE\x9A", 'kappa;' => "\xCE\xBA", 'Lambda;' => "\xCE\x9B", 'lambda;' => "\xCE\xBB", 'lang;' => "\xE3\x80\x88", 'laquo' => "\xC2\xAB", 'laquo;' => "\xC2\xAB", 'lArr;' => "\xE2\x87\x90", 'larr;' => "\xE2\x86\x90", 'lceil;' => "\xE2\x8C\x88", 'ldquo;' => "\xE2\x80\x9C", 'le;' => "\xE2\x89\xA4", 'lfloor;' => "\xE2\x8C\x8A", 'lowast;' => "\xE2\x88\x97", 'loz;' => "\xE2\x97\x8A", 'lrm;' => "\xE2\x80\x8E", 'lsaquo;' => "\xE2\x80\xB9", 'lsquo;' => "\xE2\x80\x98", 'LT' => "\x3C", 'lt' => "\x3C", 'LT;' => "\x3C", 'lt;' => "\x3C", 'macr' => "\xC2\xAF", 'macr;' => "\xC2\xAF", 'mdash;' => "\xE2\x80\x94", 'micro' => "\xC2\xB5", 'micro;' => "\xC2\xB5", 'middot' => "\xC2\xB7", 'middot;' => "\xC2\xB7", 'minus;' => "\xE2\x88\x92", 'Mu;' => "\xCE\x9C", 'mu;' => "\xCE\xBC", 'nabla;' => "\xE2\x88\x87", 'nbsp' => "\xC2\xA0", 'nbsp;' => "\xC2\xA0", 'ndash;' => "\xE2\x80\x93", 'ne;' => "\xE2\x89\xA0", 'ni;' => "\xE2\x88\x8B", 'not' => "\xC2\xAC", 'not;' => "\xC2\xAC", 'notin;' => "\xE2\x88\x89", 'nsub;' => "\xE2\x8A\x84", 'Ntilde' => "\xC3\x91", 'ntilde' => "\xC3\xB1", 'Ntilde;' => "\xC3\x91", 'ntilde;' => "\xC3\xB1", 'Nu;' => "\xCE\x9D", 'nu;' => "\xCE\xBD", 'Oacute' => "\xC3\x93", 'oacute' => "\xC3\xB3", 'Oacute;' => "\xC3\x93", 'oacute;' => "\xC3\xB3", 'Ocirc' => "\xC3\x94", 'ocirc' => "\xC3\xB4", 'Ocirc;' => "\xC3\x94", 'ocirc;' => "\xC3\xB4", 'OElig;' => "\xC5\x92", 'oelig;' => "\xC5\x93", 'Ograve' => "\xC3\x92", 'ograve' => "\xC3\xB2", 'Ograve;' => "\xC3\x92", 'ograve;' => "\xC3\xB2", 'oline;' => "\xE2\x80\xBE", 'Omega;' => "\xCE\xA9", 'omega;' => "\xCF\x89", 'Omicron;' => "\xCE\x9F", 'omicron;' => "\xCE\xBF", 'oplus;' => "\xE2\x8A\x95", 'or;' => "\xE2\x88\xA8", 'ordf' => "\xC2\xAA", 'ordf;' => "\xC2\xAA", 'ordm' => "\xC2\xBA", 'ordm;' => "\xC2\xBA", 'Oslash' => "\xC3\x98", 'oslash' => "\xC3\xB8", 'Oslash;' => "\xC3\x98", 'oslash;' => "\xC3\xB8", 'Otilde' => "\xC3\x95", 'otilde' => "\xC3\xB5", 'Otilde;' => "\xC3\x95", 'otilde;' => "\xC3\xB5", 'otimes;' => "\xE2\x8A\x97", 'Ouml' => "\xC3\x96", 'ouml' => "\xC3\xB6", 'Ouml;' => "\xC3\x96", 'ouml;' => "\xC3\xB6", 'para' => "\xC2\xB6", 'para;' => "\xC2\xB6", 'part;' => "\xE2\x88\x82", 'permil;' => "\xE2\x80\xB0", 'perp;' => "\xE2\x8A\xA5", 'Phi;' => "\xCE\xA6", 'phi;' => "\xCF\x86", 'Pi;' => "\xCE\xA0", 'pi;' => "\xCF\x80", 'piv;' => "\xCF\x96", 'plusmn' => "\xC2\xB1", 'plusmn;' => "\xC2\xB1", 'pound' => "\xC2\xA3", 'pound;' => "\xC2\xA3", 'Prime;' => "\xE2\x80\xB3", 'prime;' => "\xE2\x80\xB2", 'prod;' => "\xE2\x88\x8F", 'prop;' => "\xE2\x88\x9D", 'Psi;' => "\xCE\xA8", 'psi;' => "\xCF\x88", 'QUOT' => "\x22", 'quot' => "\x22", 'QUOT;' => "\x22", 'quot;' => "\x22", 'radic;' => "\xE2\x88\x9A", 'rang;' => "\xE3\x80\x89", 'raquo' => "\xC2\xBB", 'raquo;' => "\xC2\xBB", 'rArr;' => "\xE2\x87\x92", 'rarr;' => "\xE2\x86\x92", 'rceil;' => "\xE2\x8C\x89", 'rdquo;' => "\xE2\x80\x9D", 'real;' => "\xE2\x84\x9C", 'REG' => "\xC2\xAE", 'reg' => "\xC2\xAE", 'REG;' => "\xC2\xAE", 'reg;' => "\xC2\xAE", 'rfloor;' => "\xE2\x8C\x8B", 'Rho;' => "\xCE\xA1", 'rho;' => "\xCF\x81", 'rlm;' => "\xE2\x80\x8F", 'rsaquo;' => "\xE2\x80\xBA", 'rsquo;' => "\xE2\x80\x99", 'sbquo;' => "\xE2\x80\x9A", 'Scaron;' => "\xC5\xA0", 'scaron;' => "\xC5\xA1", 'sdot;' => "\xE2\x8B\x85", 'sect' => "\xC2\xA7", 'sect;' => "\xC2\xA7", 'shy' => "\xC2\xAD", 'shy;' => "\xC2\xAD", 'Sigma;' => "\xCE\xA3", 'sigma;' => "\xCF\x83", 'sigmaf;' => "\xCF\x82", 'sim;' => "\xE2\x88\xBC", 'spades;' => "\xE2\x99\xA0", 'sub;' => "\xE2\x8A\x82", 'sube;' => "\xE2\x8A\x86", 'sum;' => "\xE2\x88\x91", 'sup;' => "\xE2\x8A\x83", 'sup1' => "\xC2\xB9", 'sup1;' => "\xC2\xB9", 'sup2' => "\xC2\xB2", 'sup2;' => "\xC2\xB2", 'sup3' => "\xC2\xB3", 'sup3;' => "\xC2\xB3", 'supe;' => "\xE2\x8A\x87", 'szlig' => "\xC3\x9F", 'szlig;' => "\xC3\x9F", 'Tau;' => "\xCE\xA4", 'tau;' => "\xCF\x84", 'there4;' => "\xE2\x88\xB4", 'Theta;' => "\xCE\x98", 'theta;' => "\xCE\xB8", 'thetasym;' => "\xCF\x91", 'thinsp;' => "\xE2\x80\x89", 'THORN' => "\xC3\x9E", 'thorn' => "\xC3\xBE", 'THORN;' => "\xC3\x9E", 'thorn;' => "\xC3\xBE", 'tilde;' => "\xCB\x9C", 'times' => "\xC3\x97", 'times;' => "\xC3\x97", 'TRADE;' => "\xE2\x84\xA2", 'trade;' => "\xE2\x84\xA2", 'Uacute' => "\xC3\x9A", 'uacute' => "\xC3\xBA", 'Uacute;' => "\xC3\x9A", 'uacute;' => "\xC3\xBA", 'uArr;' => "\xE2\x87\x91", 'uarr;' => "\xE2\x86\x91", 'Ucirc' => "\xC3\x9B", 'ucirc' => "\xC3\xBB", 'Ucirc;' => "\xC3\x9B", 'ucirc;' => "\xC3\xBB", 'Ugrave' => "\xC3\x99", 'ugrave' => "\xC3\xB9", 'Ugrave;' => "\xC3\x99", 'ugrave;' => "\xC3\xB9", 'uml' => "\xC2\xA8", 'uml;' => "\xC2\xA8", 'upsih;' => "\xCF\x92", 'Upsilon;' => "\xCE\xA5", 'upsilon;' => "\xCF\x85", 'Uuml' => "\xC3\x9C", 'uuml' => "\xC3\xBC", 'Uuml;' => "\xC3\x9C", 'uuml;' => "\xC3\xBC", 'weierp;' => "\xE2\x84\x98", 'Xi;' => "\xCE\x9E", 'xi;' => "\xCE\xBE", 'Yacute' => "\xC3\x9D", 'yacute' => "\xC3\xBD", 'Yacute;' => "\xC3\x9D", 'yacute;' => "\xC3\xBD", 'yen' => "\xC2\xA5", 'yen;' => "\xC2\xA5", 'yuml' => "\xC3\xBF", 'Yuml;' => "\xC5\xB8", 'yuml;' => "\xC3\xBF", 'Zeta;' => "\xCE\x96", 'zeta;' => "\xCE\xB6", 'zwj;' => "\xE2\x80\x8D", 'zwnj;' => "\xE2\x80\x8C"); - - for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++) - { - $consumed = substr($this->consumed, 1); - if (isset($entities[$consumed])) - { - $match = $consumed; - } - } - - if ($match !== null) - { - $this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1); - $this->position += strlen($entities[$match]) - strlen($consumed) - 1; - } - break; - } - } -} - -/** - * IRI parser/serialiser - * - * @package SimplePie - */ -class SimplePie_IRI -{ - /** - * Scheme - * - * @access private - * @var string - */ - var $scheme; - - /** - * User Information - * - * @access private - * @var string - */ - var $userinfo; - - /** - * Host - * - * @access private - * @var string - */ - var $host; - - /** - * Port - * - * @access private - * @var string - */ - var $port; - - /** - * Path - * - * @access private - * @var string - */ - var $path; - - /** - * Query - * - * @access private - * @var string - */ - var $query; - - /** - * Fragment - * - * @access private - * @var string - */ - var $fragment; - - /** - * Whether the object represents a valid IRI - * - * @access private - * @var array - */ - var $valid = array(); - - /** - * Return the entire IRI when you try and read the object as a string - * - * @access public - * @return string - */ - function __toString() - { - return $this->get_iri(); - } - - /** - * Create a new IRI object, from a specified string - * - * @access public - * @param string $iri - * @return SimplePie_IRI - */ - function SimplePie_IRI($iri) - { - $iri = (string) $iri; - if ($iri !== '') - { - $parsed = $this->parse_iri($iri); - $this->set_scheme($parsed['scheme']); - $this->set_authority($parsed['authority']); - $this->set_path($parsed['path']); - $this->set_query($parsed['query']); - $this->set_fragment($parsed['fragment']); - } - } - - /** - * Create a new IRI object by resolving a relative IRI - * - * @static - * @access public - * @param SimplePie_IRI $base Base IRI - * @param string $relative Relative IRI - * @return SimplePie_IRI - */ - function absolutize($base, $relative) - { - $relative = (string) $relative; - if ($relative !== '') - { - $relative = new SimplePie_IRI($relative); - if ($relative->get_scheme() !== null) - { - $target = $relative; - } - elseif ($base->get_iri() !== null) - { - if ($relative->get_authority() !== null) - { - $target = $relative; - $target->set_scheme($base->get_scheme()); - } - else - { - $target = new SimplePie_IRI(''); - $target->set_scheme($base->get_scheme()); - $target->set_userinfo($base->get_userinfo()); - $target->set_host($base->get_host()); - $target->set_port($base->get_port()); - if ($relative->get_path() !== null) - { - if (strpos($relative->get_path(), '/') === 0) - { - $target->set_path($relative->get_path()); - } - elseif (($base->get_userinfo() !== null || $base->get_host() !== null || $base->get_port() !== null) && $base->get_path() === null) - { - $target->set_path('/' . $relative->get_path()); - } - elseif (($last_segment = strrpos($base->get_path(), '/')) !== false) - { - $target->set_path(substr($base->get_path(), 0, $last_segment + 1) . $relative->get_path()); - } - else - { - $target->set_path($relative->get_path()); - } - $target->set_query($relative->get_query()); - } - else - { - $target->set_path($base->get_path()); - if ($relative->get_query() !== null) - { - $target->set_query($relative->get_query()); - } - elseif ($base->get_query() !== null) - { - $target->set_query($base->get_query()); - } - } - } - $target->set_fragment($relative->get_fragment()); - } - else - { - // No base URL, just return the relative URL - $target = $relative; - } - } - else - { - $target = $base; - } - return $target; - } - - /** - * Parse an IRI into scheme/authority/path/query/fragment segments - * - * @access private - * @param string $iri - * @return array - */ - function parse_iri($iri) - { - preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/', $iri, $match); - for ($i = count($match); $i <= 9; $i++) - { - $match[$i] = ''; - } - return array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5], 'query' => $match[7], 'fragment' => $match[9]); - } - - /** - * Remove dot segments from a path - * - * @access private - * @param string $input - * @return string - */ - function remove_dot_segments($input) - { - $output = ''; - while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') - { - // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, - if (strpos($input, '../') === 0) - { - $input = substr($input, 3); - } - elseif (strpos($input, './') === 0) - { - $input = substr($input, 2); - } - // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, - elseif (strpos($input, '/./') === 0) - { - $input = substr_replace($input, '/', 0, 3); - } - elseif ($input === '/.') - { - $input = '/'; - } - // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, - elseif (strpos($input, '/../') === 0) - { - $input = substr_replace($input, '/', 0, 4); - $output = substr_replace($output, '', strrpos($output, '/')); - } - elseif ($input === '/..') - { - $input = '/'; - $output = substr_replace($output, '', strrpos($output, '/')); - } - // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, - elseif ($input === '.' || $input === '..') - { - $input = ''; - } - // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer - elseif (($pos = strpos($input, '/', 1)) !== false) - { - $output .= substr($input, 0, $pos); - $input = substr_replace($input, '', 0, $pos); - } - else - { - $output .= $input; - $input = ''; - } - } - return $output . $input; - } - - /** - * Replace invalid character with percent encoding - * - * @access private - * @param string $string Input string - * @param string $valid_chars Valid characters - * @param int $case Normalise case - * @return string - */ - function replace_invalid_with_pct_encoding($string, $valid_chars, $case = SIMPLEPIE_SAME_CASE) - { - // Normalise case - if ($case & SIMPLEPIE_LOWERCASE) - { - $string = strtolower($string); - } - elseif ($case & SIMPLEPIE_UPPERCASE) - { - $string = strtoupper($string); - } - - // Store position and string length (to avoid constantly recalculating this) - $position = 0; - $strlen = strlen($string); - - // Loop as long as we have invalid characters, advancing the position to the next invalid character - while (($position += strspn($string, $valid_chars, $position)) < $strlen) - { - // If we have a % character - if ($string[$position] === '%') - { - // If we have a pct-encoded section - if ($position + 2 < $strlen && strspn($string, '0123456789ABCDEFabcdef', $position + 1, 2) === 2) - { - // Get the the represented character - $chr = chr(hexdec(substr($string, $position + 1, 2))); - - // If the character is valid, replace the pct-encoded with the actual character while normalising case - if (strpos($valid_chars, $chr) !== false) - { - if ($case & SIMPLEPIE_LOWERCASE) - { - $chr = strtolower($chr); - } - elseif ($case & SIMPLEPIE_UPPERCASE) - { - $chr = strtoupper($chr); - } - $string = substr_replace($string, $chr, $position, 3); - $strlen -= 2; - $position++; - } - - // Otherwise just normalise the pct-encoded to uppercase - else - { - $string = substr_replace($string, strtoupper(substr($string, $position + 1, 2)), $position + 1, 2); - $position += 3; - } - } - // If we don't have a pct-encoded section, just replace the % with its own esccaped form - else - { - $string = substr_replace($string, '%25', $position, 1); - $strlen += 2; - $position += 3; - } - } - // If we have an invalid character, change into its pct-encoded form - else - { - $replacement = sprintf("%%%02X", ord($string[$position])); - $string = str_replace($string[$position], $replacement, $string); - $strlen = strlen($string); - } - } - return $string; - } - - /** - * Check if the object represents a valid IRI - * - * @access public - * @return bool - */ - function is_valid() - { - return array_sum($this->valid) === count($this->valid); - } - - /** - * Set the scheme. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @access public - * @param string $scheme - * @return bool - */ - function set_scheme($scheme) - { - if ($scheme === null || $scheme === '') - { - $this->scheme = null; - } - else - { - $len = strlen($scheme); - switch (true) - { - case $len > 1: - if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-.', 1)) - { - $this->scheme = null; - $this->valid[__FUNCTION__] = false; - return false; - } - - case $len > 0: - if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 0, 1)) - { - $this->scheme = null; - $this->valid[__FUNCTION__] = false; - return false; - } - } - $this->scheme = strtolower($scheme); - } - $this->valid[__FUNCTION__] = true; - return true; - } - - /** - * Set the authority. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @access public - * @param string $authority - * @return bool - */ - function set_authority($authority) - { - if (($userinfo_end = strrpos($authority, '@')) !== false) - { - $userinfo = substr($authority, 0, $userinfo_end); - $authority = substr($authority, $userinfo_end + 1); - } - else - { - $userinfo = null; - } - - if (($port_start = strpos($authority, ':')) !== false) - { - $port = substr($authority, $port_start + 1); - $authority = substr($authority, 0, $port_start); - } - else - { - $port = null; - } - - return $this->set_userinfo($userinfo) && $this->set_host($authority) && $this->set_port($port); - } - - /** - * Set the userinfo. - * - * @access public - * @param string $userinfo - * @return bool - */ - function set_userinfo($userinfo) - { - if ($userinfo === null || $userinfo === '') - { - $this->userinfo = null; - } - else - { - $this->userinfo = $this->replace_invalid_with_pct_encoding($userinfo, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:'); - } - $this->valid[__FUNCTION__] = true; - return true; - } - - /** - * Set the host. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @access public - * @param string $host - * @return bool - */ - function set_host($host) - { - if ($host === null || $host === '') - { - $this->host = null; - $this->valid[__FUNCTION__] = true; - return true; - } - elseif ($host[0] === '[' && substr($host, -1) === ']') - { - if (Net_IPv6::checkIPv6(substr($host, 1, -1))) - { - $this->host = $host; - $this->valid[__FUNCTION__] = true; - return true; - } - else - { - $this->host = null; - $this->valid[__FUNCTION__] = false; - return false; - } - } - else - { - $this->host = $this->replace_invalid_with_pct_encoding($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=', SIMPLEPIE_LOWERCASE); - $this->valid[__FUNCTION__] = true; - return true; - } - } - - /** - * Set the port. Returns true on success, false on failure (if there are - * any invalid characters). - * - * @access public - * @param string $port - * @return bool - */ - function set_port($port) - { - if ($port === null || $port === '') - { - $this->port = null; - $this->valid[__FUNCTION__] = true; - return true; - } - elseif (strspn($port, '0123456789') === strlen($port)) - { - $this->port = (int) $port; - $this->valid[__FUNCTION__] = true; - return true; - } - else - { - $this->port = null; - $this->valid[__FUNCTION__] = false; - return false; - } - } - - /** - * Set the path. - * - * @access public - * @param string $path - * @return bool - */ - function set_path($path) - { - if ($path === null || $path === '') - { - $this->path = null; - $this->valid[__FUNCTION__] = true; - return true; - } - elseif (substr($path, 0, 2) === '//' && $this->userinfo === null && $this->host === null && $this->port === null) - { - $this->path = null; - $this->valid[__FUNCTION__] = false; - return false; - } - else - { - $this->path = $this->replace_invalid_with_pct_encoding($path, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=@/'); - if ($this->scheme !== null) - { - $this->path = $this->remove_dot_segments($this->path); - } - $this->valid[__FUNCTION__] = true; - return true; - } - } - - /** - * Set the query. - * - * @access public - * @param string $query - * @return bool - */ - function set_query($query) - { - if ($query === null || $query === '') - { - $this->query = null; - } - else - { - $this->query = $this->replace_invalid_with_pct_encoding($query, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$\'()*+,;:@/?'); - } - $this->valid[__FUNCTION__] = true; - return true; - } - - /** - * Set the fragment. - * - * @access public - * @param string $fragment - * @return bool - */ - function set_fragment($fragment) - { - if ($fragment === null || $fragment === '') - { - $this->fragment = null; - } - else - { - $this->fragment = $this->replace_invalid_with_pct_encoding($fragment, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:@/?'); - } - $this->valid[__FUNCTION__] = true; - return true; - } - - /** - * Get the complete IRI - * - * @access public - * @return string - */ - function get_iri() - { - $iri = ''; - if ($this->scheme !== null) - { - $iri .= $this->scheme . ':'; - } - if (($authority = $this->get_authority()) !== null) - { - $iri .= '//' . $authority; - } - if ($this->path !== null) - { - $iri .= $this->path; - } - if ($this->query !== null) - { - $iri .= '?' . $this->query; - } - if ($this->fragment !== null) - { - $iri .= '#' . $this->fragment; - } - - if ($iri !== '') - { - return $iri; - } - else - { - return null; - } - } - - /** - * Get the scheme - * - * @access public - * @return string - */ - function get_scheme() - { - return $this->scheme; - } - - /** - * Get the complete authority - * - * @access public - * @return string - */ - function get_authority() - { - $authority = ''; - if ($this->userinfo !== null) - { - $authority .= $this->userinfo . '@'; - } - if ($this->host !== null) - { - $authority .= $this->host; - } - if ($this->port !== null) - { - $authority .= ':' . $this->port; - } - - if ($authority !== '') - { - return $authority; - } - else - { - return null; - } - } - - /** - * Get the user information - * - * @access public - * @return string - */ - function get_userinfo() - { - return $this->userinfo; - } - - /** - * Get the host - * - * @access public - * @return string - */ - function get_host() - { - return $this->host; - } - - /** - * Get the port - * - * @access public - * @return string - */ - function get_port() - { - return $this->port; - } - - /** - * Get the path - * - * @access public - * @return string - */ - function get_path() - { - return $this->path; - } - - /** - * Get the query - * - * @access public - * @return string - */ - function get_query() - { - return $this->query; - } - - /** - * Get the fragment - * - * @access public - * @return string - */ - function get_fragment() - { - return $this->fragment; - } -} - -/** - * Class to validate and to work with IPv6 addresses. - * - * @package SimplePie - * @copyright 2003-2005 The PHP Group - * @license http://www.opensource.org/licenses/bsd-license.php - * @link http://pear.php.net/package/Net_IPv6 - * @author Alexander Merz <alexander.merz@web.de> - * @author elfrink at introweb dot nl - * @author Josh Peck <jmp at joshpeck dot org> - * @author Geoffrey Sneddon <geoffers@gmail.com> - */ -class SimplePie_Net_IPv6 -{ - /** - * Removes a possible existing netmask specification of an IP address. - * - * @param string $ip the (compressed) IP as Hex representation - * @return string the IP the without netmask - * @since 1.1.0 - * @access public - * @static - */ - function removeNetmaskSpec($ip) - { - if (strpos($ip, '/') !== false) - { - list($addr, $nm) = explode('/', $ip); - } - else - { - $addr = $ip; - } - return $addr; - } - - /** - * Uncompresses an IPv6 address - * - * RFC 2373 allows you to compress zeros in an address to '::'. This - * function expects an valid IPv6 address and expands the '::' to - * the required zeros. - * - * Example: FF01::101 -> FF01:0:0:0:0:0:0:101 - * ::1 -> 0:0:0:0:0:0:0:1 - * - * @access public - * @static - * @param string $ip a valid IPv6-address (hex format) - * @return string the uncompressed IPv6-address (hex format) - */ - function Uncompress($ip) - { - $uip = SimplePie_Net_IPv6::removeNetmaskSpec($ip); - $c1 = -1; - $c2 = -1; - if (strpos($ip, '::') !== false) - { - list($ip1, $ip2) = explode('::', $ip); - if ($ip1 === '') - { - $c1 = -1; - } - else - { - $pos = 0; - if (($pos = substr_count($ip1, ':')) > 0) - { - $c1 = $pos; - } - else - { - $c1 = 0; - } - } - if ($ip2 === '') - { - $c2 = -1; - } - else - { - $pos = 0; - if (($pos = substr_count($ip2, ':')) > 0) - { - $c2 = $pos; - } - else - { - $c2 = 0; - } - } - if (strstr($ip2, '.')) - { - $c2++; - } - // :: - if ($c1 === -1 && $c2 === -1) - { - $uip = '0:0:0:0:0:0:0:0'; - } - // ::xxx - else if ($c1 === -1) - { - $fill = str_repeat('0:', 7 - $c2); - $uip = str_replace('::', $fill, $uip); - } - // xxx:: - else if ($c2 === -1) - { - $fill = str_repeat(':0', 7 - $c1); - $uip = str_replace('::', $fill, $uip); - } - // xxx::xxx - else - { - $fill = str_repeat(':0:', 6 - $c2 - $c1); - $uip = str_replace('::', $fill, $uip); - $uip = str_replace('::', ':', $uip); - } - } - return $uip; - } - - /** - * Splits an IPv6 address into the IPv6 and a possible IPv4 part - * - * RFC 2373 allows you to note the last two parts of an IPv6 address as - * an IPv4 compatible address - * - * Example: 0:0:0:0:0:0:13.1.68.3 - * 0:0:0:0:0:FFFF:129.144.52.38 - * - * @access public - * @static - * @param string $ip a valid IPv6-address (hex format) - * @return array [0] contains the IPv6 part, [1] the IPv4 part (hex format) - */ - function SplitV64($ip) - { - $ip = SimplePie_Net_IPv6::Uncompress($ip); - if (strstr($ip, '.')) - { - $pos = strrpos($ip, ':'); - $ip[$pos] = '_'; - $ipPart = explode('_', $ip); - return $ipPart; - } - else - { - return array($ip, ''); - } - } - - /** - * Checks an IPv6 address - * - * Checks if the given IP is IPv6-compatible - * - * @access public - * @static - * @param string $ip a valid IPv6-address - * @return bool true if $ip is an IPv6 address - */ - function checkIPv6($ip) - { - $ipPart = SimplePie_Net_IPv6::SplitV64($ip); - $count = 0; - if (!empty($ipPart[0])) - { - $ipv6 = explode(':', $ipPart[0]); - for ($i = 0; $i < count($ipv6); $i++) - { - $dec = hexdec($ipv6[$i]); - $hex = strtoupper(preg_replace('/^[0]{1,3}(.*[0-9a-fA-F])$/', '\\1', $ipv6[$i])); - if ($ipv6[$i] >= 0 && $dec <= 65535 && $hex === strtoupper(dechex($dec))) - { - $count++; - } - } - if ($count === 8) - { - return true; - } - elseif ($count === 6 && !empty($ipPart[1])) - { - $ipv4 = explode('.', $ipPart[1]); - $count = 0; - foreach ($ipv4 as $ipv4_part) - { - if ($ipv4_part >= 0 && $ipv4_part <= 255 && preg_match('/^\d{1,3}$/', $ipv4_part)) - { - $count++; - } - } - if ($count === 4) - { - return true; - } - } - else - { - return false; - } - - } - else - { - return false; - } - } -} - -/** - * Date Parser - * - * @package SimplePie - */ -class SimplePie_Parse_Date -{ - /** - * Input data - * - * @access protected - * @var string - */ - var $date; - - /** - * List of days, calendar day name => ordinal day number in the week - * - * @access protected - * @var array - */ - var $day = array( - // English - 'mon' => 1, - 'monday' => 1, - 'tue' => 2, - 'tuesday' => 2, - 'wed' => 3, - 'wednesday' => 3, - 'thu' => 4, - 'thursday' => 4, - 'fri' => 5, - 'friday' => 5, - 'sat' => 6, - 'saturday' => 6, - 'sun' => 7, - 'sunday' => 7, - // Dutch - 'maandag' => 1, - 'dinsdag' => 2, - 'woensdag' => 3, - 'donderdag' => 4, - 'vrijdag' => 5, - 'zaterdag' => 6, - 'zondag' => 7, - // French - 'lundi' => 1, - 'mardi' => 2, - 'mercredi' => 3, - 'jeudi' => 4, - 'vendredi' => 5, - 'samedi' => 6, - 'dimanche' => 7, - // German - 'montag' => 1, - 'dienstag' => 2, - 'mittwoch' => 3, - 'donnerstag' => 4, - 'freitag' => 5, - 'samstag' => 6, - 'sonnabend' => 6, - 'sonntag' => 7, - // Italian - 'lunedì' => 1, - 'martedì' => 2, - 'mercoledì' => 3, - 'giovedì' => 4, - 'venerdì' => 5, - 'sabato' => 6, - 'domenica' => 7, - // Spanish - 'lunes' => 1, - 'martes' => 2, - 'miércoles' => 3, - 'jueves' => 4, - 'viernes' => 5, - 'sábado' => 6, - 'domingo' => 7, - // Finnish - 'maanantai' => 1, - 'tiistai' => 2, - 'keskiviikko' => 3, - 'torstai' => 4, - 'perjantai' => 5, - 'lauantai' => 6, - 'sunnuntai' => 7, - // Hungarian - 'hétfő' => 1, - 'kedd' => 2, - 'szerda' => 3, - 'csütörtok' => 4, - 'péntek' => 5, - 'szombat' => 6, - 'vasárnap' => 7, - // Greek - 'Δευ' => 1, - 'Τρι' => 2, - 'Τετ' => 3, - 'Πεμ' => 4, - 'Παρ' => 5, - 'Σαβ' => 6, - 'Κυρ' => 7, - ); - - /** - * List of months, calendar month name => calendar month number - * - * @access protected - * @var array - */ - var $month = array( - // English - 'jan' => 1, - 'january' => 1, - 'feb' => 2, - 'february' => 2, - 'mar' => 3, - 'march' => 3, - 'apr' => 4, - 'april' => 4, - 'may' => 5, - // No long form of May - 'jun' => 6, - 'june' => 6, - 'jul' => 7, - 'july' => 7, - 'aug' => 8, - 'august' => 8, - 'sep' => 9, - 'september' => 8, - 'oct' => 10, - 'october' => 10, - 'nov' => 11, - 'november' => 11, - 'dec' => 12, - 'december' => 12, - // Dutch - 'januari' => 1, - 'februari' => 2, - 'maart' => 3, - 'april' => 4, - 'mei' => 5, - 'juni' => 6, - 'juli' => 7, - 'augustus' => 8, - 'september' => 9, - 'oktober' => 10, - 'november' => 11, - 'december' => 12, - // French - 'janvier' => 1, - 'février' => 2, - 'mars' => 3, - 'avril' => 4, - 'mai' => 5, - 'juin' => 6, - 'juillet' => 7, - 'août' => 8, - 'septembre' => 9, - 'octobre' => 10, - 'novembre' => 11, - 'décembre' => 12, - // German - 'januar' => 1, - 'februar' => 2, - 'märz' => 3, - 'april' => 4, - 'mai' => 5, - 'juni' => 6, - 'juli' => 7, - 'august' => 8, - 'september' => 9, - 'oktober' => 10, - 'november' => 11, - 'dezember' => 12, - // Italian - 'gennaio' => 1, - 'febbraio' => 2, - 'marzo' => 3, - 'aprile' => 4, - 'maggio' => 5, - 'giugno' => 6, - 'luglio' => 7, - 'agosto' => 8, - 'settembre' => 9, - 'ottobre' => 10, - 'novembre' => 11, - 'dicembre' => 12, - // Spanish - 'enero' => 1, - 'febrero' => 2, - 'marzo' => 3, - 'abril' => 4, - 'mayo' => 5, - 'junio' => 6, - 'julio' => 7, - 'agosto' => 8, - 'septiembre' => 9, - 'setiembre' => 9, - 'octubre' => 10, - 'noviembre' => 11, - 'diciembre' => 12, - // Finnish - 'tammikuu' => 1, - 'helmikuu' => 2, - 'maaliskuu' => 3, - 'huhtikuu' => 4, - 'toukokuu' => 5, - 'kesäkuu' => 6, - 'heinäkuu' => 7, - 'elokuu' => 8, - 'suuskuu' => 9, - 'lokakuu' => 10, - 'marras' => 11, - 'joulukuu' => 12, - // Hungarian - 'január' => 1, - 'február' => 2, - 'március' => 3, - 'április' => 4, - 'május' => 5, - 'június' => 6, - 'július' => 7, - 'augusztus' => 8, - 'szeptember' => 9, - 'október' => 10, - 'november' => 11, - 'december' => 12, - // Greek - 'Ιαν' => 1, - 'Φεβ' => 2, - 'Μάώ' => 3, - 'Μαώ' => 3, - 'Απρ' => 4, - 'Μάι' => 5, - 'Μαϊ' => 5, - 'Μαι' => 5, - 'Ιούν' => 6, - 'Ιον' => 6, - 'Ιούλ' => 7, - 'Ιολ' => 7, - 'Αύγ' => 8, - 'Αυγ' => 8, - 'Σεπ' => 9, - 'Οκτ' => 10, - 'Νοέ' => 11, - 'Δεκ' => 12, - ); - - /** - * List of timezones, abbreviation => offset from UTC - * - * @access protected - * @var array - */ - var $timezone = array( - 'ACDT' => 37800, - 'ACIT' => 28800, - 'ACST' => 34200, - 'ACT' => -18000, - 'ACWDT' => 35100, - 'ACWST' => 31500, - 'AEDT' => 39600, - 'AEST' => 36000, - 'AFT' => 16200, - 'AKDT' => -28800, - 'AKST' => -32400, - 'AMDT' => 18000, - 'AMT' => -14400, - 'ANAST' => 46800, - 'ANAT' => 43200, - 'ART' => -10800, - 'AZOST' => -3600, - 'AZST' => 18000, - 'AZT' => 14400, - 'BIOT' => 21600, - 'BIT' => -43200, - 'BOT' => -14400, - 'BRST' => -7200, - 'BRT' => -10800, - 'BST' => 3600, - 'BTT' => 21600, - 'CAST' => 18000, - 'CAT' => 7200, - 'CCT' => 23400, - 'CDT' => -18000, - 'CEDT' => 7200, - 'CET' => 3600, - 'CGST' => -7200, - 'CGT' => -10800, - 'CHADT' => 49500, - 'CHAST' => 45900, - 'CIST' => -28800, - 'CKT' => -36000, - 'CLDT' => -10800, - 'CLST' => -14400, - 'COT' => -18000, - 'CST' => -21600, - 'CVT' => -3600, - 'CXT' => 25200, - 'DAVT' => 25200, - 'DTAT' => 36000, - 'EADT' => -18000, - 'EAST' => -21600, - 'EAT' => 10800, - 'ECT' => -18000, - 'EDT' => -14400, - 'EEST' => 10800, - 'EET' => 7200, - 'EGT' => -3600, - 'EKST' => 21600, - 'EST' => -18000, - 'FJT' => 43200, - 'FKDT' => -10800, - 'FKST' => -14400, - 'FNT' => -7200, - 'GALT' => -21600, - 'GEDT' => 14400, - 'GEST' => 10800, - 'GFT' => -10800, - 'GILT' => 43200, - 'GIT' => -32400, - 'GST' => 14400, - 'GST' => -7200, - 'GYT' => -14400, - 'HAA' => -10800, - 'HAC' => -18000, - 'HADT' => -32400, - 'HAE' => -14400, - 'HAP' => -25200, - 'HAR' => -21600, - 'HAST' => -36000, - 'HAT' => -9000, - 'HAY' => -28800, - 'HKST' => 28800, - 'HMT' => 18000, - 'HNA' => -14400, - 'HNC' => -21600, - 'HNE' => -18000, - 'HNP' => -28800, - 'HNR' => -25200, - 'HNT' => -12600, - 'HNY' => -32400, - 'IRDT' => 16200, - 'IRKST' => 32400, - 'IRKT' => 28800, - 'IRST' => 12600, - 'JFDT' => -10800, - 'JFST' => -14400, - 'JST' => 32400, - 'KGST' => 21600, - 'KGT' => 18000, - 'KOST' => 39600, - 'KOVST' => 28800, - 'KOVT' => 25200, - 'KRAST' => 28800, - 'KRAT' => 25200, - 'KST' => 32400, - 'LHDT' => 39600, - 'LHST' => 37800, - 'LINT' => 50400, - 'LKT' => 21600, - 'MAGST' => 43200, - 'MAGT' => 39600, - 'MAWT' => 21600, - 'MDT' => -21600, - 'MESZ' => 7200, - 'MEZ' => 3600, - 'MHT' => 43200, - 'MIT' => -34200, - 'MNST' => 32400, - 'MSDT' => 14400, - 'MSST' => 10800, - 'MST' => -25200, - 'MUT' => 14400, - 'MVT' => 18000, - 'MYT' => 28800, - 'NCT' => 39600, - 'NDT' => -9000, - 'NFT' => 41400, - 'NMIT' => 36000, - 'NOVST' => 25200, - 'NOVT' => 21600, - 'NPT' => 20700, - 'NRT' => 43200, - 'NST' => -12600, - 'NUT' => -39600, - 'NZDT' => 46800, - 'NZST' => 43200, - 'OMSST' => 25200, - 'OMST' => 21600, - 'PDT' => -25200, - 'PET' => -18000, - 'PETST' => 46800, - 'PETT' => 43200, - 'PGT' => 36000, - 'PHOT' => 46800, - 'PHT' => 28800, - 'PKT' => 18000, - 'PMDT' => -7200, - 'PMST' => -10800, - 'PONT' => 39600, - 'PST' => -28800, - 'PWT' => 32400, - 'PYST' => -10800, - 'PYT' => -14400, - 'RET' => 14400, - 'ROTT' => -10800, - 'SAMST' => 18000, - 'SAMT' => 14400, - 'SAST' => 7200, - 'SBT' => 39600, - 'SCDT' => 46800, - 'SCST' => 43200, - 'SCT' => 14400, - 'SEST' => 3600, - 'SGT' => 28800, - 'SIT' => 28800, - 'SRT' => -10800, - 'SST' => -39600, - 'SYST' => 10800, - 'SYT' => 7200, - 'TFT' => 18000, - 'THAT' => -36000, - 'TJT' => 18000, - 'TKT' => -36000, - 'TMT' => 18000, - 'TOT' => 46800, - 'TPT' => 32400, - 'TRUT' => 36000, - 'TVT' => 43200, - 'TWT' => 28800, - 'UYST' => -7200, - 'UYT' => -10800, - 'UZT' => 18000, - 'VET' => -14400, - 'VLAST' => 39600, - 'VLAT' => 36000, - 'VOST' => 21600, - 'VUT' => 39600, - 'WAST' => 7200, - 'WAT' => 3600, - 'WDT' => 32400, - 'WEST' => 3600, - 'WFT' => 43200, - 'WIB' => 25200, - 'WIT' => 32400, - 'WITA' => 28800, - 'WKST' => 18000, - 'WST' => 28800, - 'YAKST' => 36000, - 'YAKT' => 32400, - 'YAPT' => 36000, - 'YEKST' => 21600, - 'YEKT' => 18000, - ); - - /** - * Cached PCRE for SimplePie_Parse_Date::$day - * - * @access protected - * @var string - */ - var $day_pcre; - - /** - * Cached PCRE for SimplePie_Parse_Date::$month - * - * @access protected - * @var string - */ - var $month_pcre; - - /** - * Array of user-added callback methods - * - * @access private - * @var array - */ - var $built_in = array(); - - /** - * Array of user-added callback methods - * - * @access private - * @var array - */ - var $user = array(); - - /** - * Create new SimplePie_Parse_Date object, and set self::day_pcre, - * self::month_pcre, and self::built_in - * - * @access private - */ - function SimplePie_Parse_Date() - { - $this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')'; - $this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')'; - - static $cache; - if (!isset($cache[get_class($this)])) - { - $all_methods = get_class_methods($this); - - foreach ($all_methods as $method) - { - if (strtolower(substr($method, 0, 5)) === 'date_') - { - $cache[get_class($this)][] = $method; - } - } - } - - foreach ($cache[get_class($this)] as $method) - { - $this->built_in[] = $method; - } - } - - /** - * Get the object - * - * @access public - */ - function get() - { - static $object; - if (!$object) - { - $object = new SimplePie_Parse_Date; - } - return $object; - } - - /** - * Parse a date - * - * @final - * @access public - * @param string $date Date to parse - * @return int Timestamp corresponding to date string, or false on failure - */ - function parse($date) - { - foreach ($this->user as $method) - { - if (($returned = call_user_func($method, $date)) !== false) - { - return $returned; - } - } - - foreach ($this->built_in as $method) - { - if (($returned = call_user_func(array(&$this, $method), $date)) !== false) - { - return $returned; - } - } - - return false; - } - - /** - * Add a callback method to parse a date - * - * @final - * @access public - * @param callback $callback - */ - function add_callback($callback) - { - if (is_callable($callback)) - { - $this->user[] = $callback; - } - else - { - trigger_error('User-supplied function must be a valid callback', E_USER_WARNING); - } - } - - /** - * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as - * well as allowing any of upper or lower case "T", horizontal tabs, or - * spaces to be used as the time seperator (including more than one)) - * - * @access protected - * @return int Timestamp - */ - function date_w3cdtf($date) - { - static $pcre; - if (!$pcre) - { - $year = '([0-9]{4})'; - $month = $day = $hour = $minute = $second = '([0-9]{2})'; - $decimal = '([0-9]*)'; - $zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))'; - $pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/'; - } - if (preg_match($pcre, $date, $match)) - { - /* - Capturing subpatterns: - 1: Year - 2: Month - 3: Day - 4: Hour - 5: Minute - 6: Second - 7: Decimal fraction of a second - 8: Zulu - 9: Timezone ± - 10: Timezone hours - 11: Timezone minutes - */ - - // Fill in empty matches - for ($i = count($match); $i <= 3; $i++) - { - $match[$i] = '1'; - } - - for ($i = count($match); $i <= 7; $i++) - { - $match[$i] = '0'; - } - - // Numeric timezone - if (isset($match[9]) && $match[9] !== '') - { - $timezone = $match[10] * 3600; - $timezone += $match[11] * 60; - if ($match[9] === '-') - { - $timezone = 0 - $timezone; - } - } - else - { - $timezone = 0; - } - - // Convert the number of seconds to an integer, taking decimals into account - $second = round($match[6] + $match[7] / pow(10, strlen($match[7]))); - - return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone; - } - else - { - return false; - } - } - - /** - * Remove RFC822 comments - * - * @access protected - * @param string $data Data to strip comments from - * @return string Comment stripped string - */ - function remove_rfc2822_comments($string) - { - $string = (string) $string; - $position = 0; - $length = strlen($string); - $depth = 0; - - $output = ''; - - while ($position < $length && ($pos = strpos($string, '(', $position)) !== false) - { - $output .= substr($string, $position, $pos - $position); - $position = $pos + 1; - if ($string[$pos - 1] !== '\\') - { - $depth++; - while ($depth && $position < $length) - { - $position += strcspn($string, '()', $position); - if ($string[$position - 1] === '\\') - { - $position++; - continue; - } - elseif (isset($string[$position])) - { - switch ($string[$position]) - { - case '(': - $depth++; - break; - - case ')': - $depth--; - break; - } - $position++; - } - else - { - break; - } - } - } - else - { - $output .= '('; - } - } - $output .= substr($string, $position); - - return $output; - } - - /** - * Parse RFC2822's date format - * - * @access protected - * @return int Timestamp - */ - function date_rfc2822($date) - { - static $pcre; - if (!$pcre) - { - $wsp = '[\x09\x20]'; - $fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)'; - $optional_fws = $fws . '?'; - $day_name = $this->day_pcre; - $month = $this->month_pcre; - $day = '([0-9]{1,2})'; - $hour = $minute = $second = '([0-9]{2})'; - $year = '([0-9]{2,4})'; - $num_zone = '([+\-])([0-9]{2})([0-9]{2})'; - $character_zone = '([A-Z]{1,5})'; - $zone = '(?:' . $num_zone . '|' . $character_zone . ')'; - $pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i'; - } - if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match)) - { - /* - Capturing subpatterns: - 1: Day name - 2: Day - 3: Month - 4: Year - 5: Hour - 6: Minute - 7: Second - 8: Timezone ± - 9: Timezone hours - 10: Timezone minutes - 11: Alphabetic timezone - */ - - // Find the month number - $month = $this->month[strtolower($match[3])]; - - // Numeric timezone - if ($match[8] !== '') - { - $timezone = $match[9] * 3600; - $timezone += $match[10] * 60; - if ($match[8] === '-') - { - $timezone = 0 - $timezone; - } - } - // Character timezone - elseif (isset($this->timezone[strtoupper($match[11])])) - { - $timezone = $this->timezone[strtoupper($match[11])]; - } - // Assume everything else to be -0000 - else - { - $timezone = 0; - } - - // Deal with 2/3 digit years - if ($match[4] < 50) - { - $match[4] += 2000; - } - elseif ($match[4] < 1000) - { - $match[4] += 1900; - } - - // Second is optional, if it is empty set it to zero - if ($match[7] !== '') - { - $second = $match[7]; - } - else - { - $second = 0; - } - - return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone; - } - else - { - return false; - } - } - - /** - * Parse RFC850's date format - * - * @access protected - * @return int Timestamp - */ - function date_rfc850($date) - { - static $pcre; - if (!$pcre) - { - $space = '[\x09\x20]+'; - $day_name = $this->day_pcre; - $month = $this->month_pcre; - $day = '([0-9]{1,2})'; - $year = $hour = $minute = $second = '([0-9]{2})'; - $zone = '([A-Z]{1,5})'; - $pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i'; - } - if (preg_match($pcre, $date, $match)) - { - /* - Capturing subpatterns: - 1: Day name - 2: Day - 3: Month - 4: Year - 5: Hour - 6: Minute - 7: Second - 8: Timezone - */ - - // Month - $month = $this->month[strtolower($match[3])]; - - // Character timezone - if (isset($this->timezone[strtoupper($match[8])])) - { - $timezone = $this->timezone[strtoupper($match[8])]; - } - // Assume everything else to be -0000 - else - { - $timezone = 0; - } - - // Deal with 2 digit year - if ($match[4] < 50) - { - $match[4] += 2000; - } - else - { - $match[4] += 1900; - } - - return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone; - } - else - { - return false; - } - } - - /** - * Parse C99's asctime()'s date format - * - * @access protected - * @return int Timestamp - */ - function date_asctime($date) - { - static $pcre; - if (!$pcre) - { - $space = '[\x09\x20]+'; - $wday_name = $this->day_pcre; - $mon_name = $this->month_pcre; - $day = '([0-9]{1,2})'; - $hour = $sec = $min = '([0-9]{2})'; - $year = '([0-9]{4})'; - $terminator = '\x0A?\x00?'; - $pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i'; - } - if (preg_match($pcre, $date, $match)) - { - /* - Capturing subpatterns: - 1: Day name - 2: Month - 3: Day - 4: Hour - 5: Minute - 6: Second - 7: Year - */ - - $month = $this->month[strtolower($match[2])]; - return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]); - } - else - { - return false; - } - } - - /** - * Parse dates using strtotime() - * - * @access protected - * @return int Timestamp - */ - function date_strtotime($date) - { - $strtotime = strtotime($date); - if ($strtotime === -1 || $strtotime === false) - { - return false; - } - else - { - return $strtotime; - } - } -} - -/** - * Content-type sniffing - * - * @package SimplePie - */ -class SimplePie_Content_Type_Sniffer -{ - /** - * File object - * - * @var SimplePie_File - * @access private - */ - var $file; - - /** - * Create an instance of the class with the input file - * - * @access public - * @param SimplePie_Content_Type_Sniffer $file Input file - */ - function SimplePie_Content_Type_Sniffer($file) - { - $this->file = $file; - } - - /** - * Get the Content-Type of the specified file - * - * @access public - * @return string Actual Content-Type - */ - function get_type() - { - if (isset($this->file->headers['content-type'])) - { - if (!isset($this->file->headers['content-encoding']) - && ($this->file->headers['content-type'] === 'text/plain' - || $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1' - || $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1')) - { - return $this->text_or_binary(); - } - - if (($pos = strpos($this->file->headers['content-type'], ';')) !== false) - { - $official = substr($this->file->headers['content-type'], 0, $pos); - } - else - { - $official = $this->file->headers['content-type']; - } - $official = strtolower($official); - - if ($official === 'unknown/unknown' - || $official === 'application/unknown') - { - return $this->unknown(); - } - elseif (substr($official, -4) === '+xml' - || $official === 'text/xml' - || $official === 'application/xml') - { - return $official; - } - elseif (substr($official, 0, 6) === 'image/') - { - if ($return = $this->image()) - { - return $return; - } - else - { - return $official; - } - } - elseif ($official === 'text/html') - { - return $this->feed_or_html(); - } - else - { - return $official; - } - } - else - { - return $this->unknown(); - } - } - - /** - * Sniff text or binary - * - * @access private - * @return string Actual Content-Type - */ - function text_or_binary() - { - if (substr($this->file->body, 0, 2) === "\xFE\xFF" - || substr($this->file->body, 0, 2) === "\xFF\xFE" - || substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF" - || substr($this->file->body, 0, 3) === "\xEF\xBB\xBF") - { - return 'text/plain'; - } - elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body)) - { - return 'application/octect-stream'; - } - else - { - return 'text/plain'; - } - } - - /** - * Sniff unknown - * - * @access private - * @return string Actual Content-Type - */ - function unknown() - { - $ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20"); - if (strtolower(substr($this->file->body, $ws, 14)) === '<!doctype html' - || strtolower(substr($this->file->body, $ws, 5)) === '<html' - || strtolower(substr($this->file->body, $ws, 7)) === '<script') - { - return 'text/html'; - } - elseif (substr($this->file->body, 0, 5) === '%PDF-') - { - return 'application/pdf'; - } - elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-') - { - return 'application/postscript'; - } - elseif (substr($this->file->body, 0, 6) === 'GIF87a' - || substr($this->file->body, 0, 6) === 'GIF89a') - { - return 'image/gif'; - } - elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") - { - return 'image/png'; - } - elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") - { - return 'image/jpeg'; - } - elseif (substr($this->file->body, 0, 2) === "\x42\x4D") - { - return 'image/bmp'; - } - else - { - return $this->text_or_binary(); - } - } - - /** - * Sniff images - * - * @access private - * @return string Actual Content-Type - */ - function image() - { - if (substr($this->file->body, 0, 6) === 'GIF87a' - || substr($this->file->body, 0, 6) === 'GIF89a') - { - return 'image/gif'; - } - elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") - { - return 'image/png'; - } - elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF") - { - return 'image/jpeg'; - } - elseif (substr($this->file->body, 0, 2) === "\x42\x4D") - { - return 'image/bmp'; - } - else - { - return false; - } - } - - /** - * Sniff HTML - * - * @access private - * @return string Actual Content-Type - */ - function feed_or_html() - { - $len = strlen($this->file->body); - $pos = strspn($this->file->body, "\x09\x0A\x0D\x20"); - - while ($pos < $len) - { - switch ($this->file->body[$pos]) - { - case "\x09": - case "\x0A": - case "\x0D": - case "\x20": - $pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos); - continue 2; - - case '<': - $pos++; - break; - - default: - return 'text/html'; - } - - if (substr($this->file->body, $pos, 3) === '!--') - { - $pos += 3; - if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false) - { - $pos += 3; - } - else - { - return 'text/html'; - } - } - elseif (substr($this->file->body, $pos, 1) === '!') - { - if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false) - { - $pos++; - } - else - { - return 'text/html'; - } - } - elseif (substr($this->file->body, $pos, 1) === '?') - { - if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false) - { - $pos += 2; - } - else - { - return 'text/html'; - } - } - elseif (substr($this->file->body, $pos, 3) === 'rss' - || substr($this->file->body, $pos, 7) === 'rdf:RDF') - { - return 'application/rss+xml'; - } - elseif (substr($this->file->body, $pos, 4) === 'feed') - { - return 'application/atom+xml'; - } - else - { - return 'text/html'; - } - } - - return 'text/html'; - } -} - -/** - * Parses the XML Declaration - * - * @package SimplePie - */ -class SimplePie_XML_Declaration_Parser -{ - /** - * XML Version - * - * @access public - * @var string - */ - var $version = '1.0'; - - /** - * Encoding - * - * @access public - * @var string - */ - var $encoding = 'UTF-8'; - - /** - * Standalone - * - * @access public - * @var bool - */ - var $standalone = false; - - /** - * Current state of the state machine - * - * @access private - * @var string - */ - var $state = 'before_version_name'; - - /** - * Input data - * - * @access private - * @var string - */ - var $data = ''; - - /** - * Input data length (to avoid calling strlen() everytime this is needed) - * - * @access private - * @var int - */ - var $data_length = 0; - - /** - * Current position of the pointer - * - * @var int - * @access private - */ - var $position = 0; - - /** - * Create an instance of the class with the input data - * - * @access public - * @param string $data Input data - */ - function SimplePie_XML_Declaration_Parser($data) - { - $this->data = $data; - $this->data_length = strlen($this->data); - } - - /** - * Parse the input data - * - * @access public - * @return bool true on success, false on failure - */ - function parse() - { - while ($this->state && $this->state !== 'emit' && $this->has_data()) - { - $state = $this->state; - $this->$state(); - } - $this->data = ''; - if ($this->state === 'emit') - { - return true; - } - else - { - $this->version = ''; - $this->encoding = ''; - $this->standalone = ''; - return false; - } - } - - /** - * Check whether there is data beyond the pointer - * - * @access private - * @return bool true if there is further data, false if not - */ - function has_data() - { - return (bool) ($this->position < $this->data_length); - } - - /** - * Advance past any whitespace - * - * @return int Number of whitespace characters passed - */ - function skip_whitespace() - { - $whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position); - $this->position += $whitespace; - return $whitespace; - } - - /** - * Read value - */ - function get_value() - { - $quote = substr($this->data, $this->position, 1); - if ($quote === '"' || $quote === "'") - { - $this->position++; - $len = strcspn($this->data, $quote, $this->position); - if ($this->has_data()) - { - $value = substr($this->data, $this->position, $len); - $this->position += $len + 1; - return $value; - } - } - return false; - } - - function before_version_name() - { - if ($this->skip_whitespace()) - { - $this->state = 'version_name'; - } - else - { - $this->state = false; - } - } - - function version_name() - { - if (substr($this->data, $this->position, 7) === 'version') - { - $this->position += 7; - $this->skip_whitespace(); - $this->state = 'version_equals'; - } - else - { - $this->state = false; - } - } - - function version_equals() - { - if (substr($this->data, $this->position, 1) === '=') - { - $this->position++; - $this->skip_whitespace(); - $this->state = 'version_value'; - } - else - { - $this->state = false; - } - } - - function version_value() - { - if ($this->version = $this->get_value()) - { - $this->skip_whitespace(); - if ($this->has_data()) - { - $this->state = 'encoding_name'; - } - else - { - $this->state = 'emit'; - } - } - else - { - $this->state = false; - } - } - - function encoding_name() - { - if (substr($this->data, $this->position, 8) === 'encoding') - { - $this->position += 8; - $this->skip_whitespace(); - $this->state = 'encoding_equals'; - } - else - { - $this->state = 'standalone_name'; - } - } - - function encoding_equals() - { - if (substr($this->data, $this->position, 1) === '=') - { - $this->position++; - $this->skip_whitespace(); - $this->state = 'encoding_value'; - } - else - { - $this->state = false; - } - } - - function encoding_value() - { - if ($this->encoding = $this->get_value()) - { - $this->skip_whitespace(); - if ($this->has_data()) - { - $this->state = 'standalone_name'; - } - else - { - $this->state = 'emit'; - } - } - else - { - $this->state = false; - } - } - - function standalone_name() - { - if (substr($this->data, $this->position, 10) === 'standalone') - { - $this->position += 10; - $this->skip_whitespace(); - $this->state = 'standalone_equals'; - } - else - { - $this->state = false; - } - } - - function standalone_equals() - { - if (substr($this->data, $this->position, 1) === '=') - { - $this->position++; - $this->skip_whitespace(); - $this->state = 'standalone_value'; - } - else - { - $this->state = false; - } - } - - function standalone_value() - { - if ($standalone = $this->get_value()) - { - switch ($standalone) - { - case 'yes': - $this->standalone = true; - break; - - case 'no': - $this->standalone = false; - break; - - default: - $this->state = false; - return; - } - - $this->skip_whitespace(); - if ($this->has_data()) - { - $this->state = false; - } - else - { - $this->state = 'emit'; - } - } - else - { - $this->state = false; - } - } -} - -class SimplePie_Locator -{ - var $useragent; - var $timeout; - var $file; - var $local = array(); - var $elsewhere = array(); - var $file_class = 'SimplePie_File'; - var $cached_entities = array(); - var $http_base; - var $base; - var $base_location = 0; - var $checked_feeds = 0; - var $max_checked_feeds = 10; - var $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer'; - - function SimplePie_Locator(&$file, $timeout = 10, $useragent = null, $file_class = 'SimplePie_File', $max_checked_feeds = 10, $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer') - { - $this->file =& $file; - $this->file_class = $file_class; - $this->useragent = $useragent; - $this->timeout = $timeout; - $this->max_checked_feeds = $max_checked_feeds; - $this->content_type_sniffer_class = $content_type_sniffer_class; - } - - function find($type = SIMPLEPIE_LOCATOR_ALL, &$working) - { - if ($this->is_feed($this->file)) - { - return $this->file; - } - - if ($this->file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) - { - $sniffer = new $this->content_type_sniffer_class($this->file); - if ($sniffer->get_type() !== 'text/html') - { - return null; - } - } - - if ($type & ~SIMPLEPIE_LOCATOR_NONE) - { - $this->get_base(); - } - - if ($type & SIMPLEPIE_LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery()) - { - return $working[0]; - } - - if ($type & (SIMPLEPIE_LOCATOR_LOCAL_EXTENSION | SIMPLEPIE_LOCATOR_LOCAL_BODY | SIMPLEPIE_LOCATOR_REMOTE_EXTENSION | SIMPLEPIE_LOCATOR_REMOTE_BODY) && $this->get_links()) - { - if ($type & SIMPLEPIE_LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local)) - { - return $working; - } - - if ($type & SIMPLEPIE_LOCATOR_LOCAL_BODY && $working = $this->body($this->local)) - { - return $working; - } - - if ($type & SIMPLEPIE_LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere)) - { - return $working; - } - - if ($type & SIMPLEPIE_LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere)) - { - return $working; - } - } - return null; - } - - function is_feed(&$file) - { - if ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE) - { - $sniffer = new $this->content_type_sniffer_class($file); - $sniffed = $sniffer->get_type(); - if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml'))) - { - return true; - } - else - { - return false; - } - } - elseif ($file->method & SIMPLEPIE_FILE_SOURCE_LOCAL) - { - return true; - } - else - { - return false; - } - } - - function get_base() - { - $this->http_base = $this->file->url; - $this->base = $this->http_base; - $elements = SimplePie_Misc::get_element('base', $this->file->body); - foreach ($elements as $element) - { - if ($element['attribs']['href']['data'] !== '') - { - $this->base = SimplePie_Misc::absolutize_url(trim($element['attribs']['href']['data']), $this->http_base); - $this->base_location = $element['offset']; - break; - } - } - } - - function autodiscovery() - { - $links = array_merge(SimplePie_Misc::get_element('link', $this->file->body), SimplePie_Misc::get_element('a', $this->file->body), SimplePie_Misc::get_element('area', $this->file->body)); - $done = array(); - $feeds = array(); - foreach ($links as $link) - { - if ($this->checked_feeds === $this->max_checked_feeds) - { - break; - } - if (isset($link['attribs']['href']['data']) && isset($link['attribs']['rel']['data'])) - { - $rel = array_unique(SimplePie_Misc::space_seperated_tokens(strtolower($link['attribs']['rel']['data']))); - - if ($this->base_location < $link['offset']) - { - $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base); - } - else - { - $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base); - } - - if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !empty($link['attribs']['type']['data']) && in_array(strtolower(SimplePie_Misc::parse_mime($link['attribs']['type']['data'])), array('application/rss+xml', 'application/atom+xml'))) && !isset($feeds[$href])) - { - $this->checked_feeds++; - $feed = new $this->file_class($href, $this->timeout, 5, null, $this->useragent); - if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) - { - $feeds[$href] = $feed; - } - } - $done[] = $href; - } - } - - if (!empty($feeds)) - { - return array_values($feeds); - } - else { - return null; - } - } - - function get_links() - { - $links = SimplePie_Misc::get_element('a', $this->file->body); - foreach ($links as $link) - { - if (isset($link['attribs']['href']['data'])) - { - $href = trim($link['attribs']['href']['data']); - $parsed = SimplePie_Misc::parse_url($href); - if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme'])) - { - if ($this->base_location < $link['offset']) - { - $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base); - } - else - { - $href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base); - } - - $current = SimplePie_Misc::parse_url($this->file->url); - - if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority']) - { - $this->local[] = $href; - } - else - { - $this->elsewhere[] = $href; - } - } - } - } - $this->local = array_unique($this->local); - $this->elsewhere = array_unique($this->elsewhere); - if (!empty($this->local) || !empty($this->elsewhere)) - { - return true; - } - return null; - } - - function extension(&$array) - { - foreach ($array as $key => $value) - { - if ($this->checked_feeds === $this->max_checked_feeds) - { - break; - } - if (in_array(strtolower(strrchr($value, '.')), array('.rss', '.rdf', '.atom', '.xml'))) - { - $this->checked_feeds++; - $feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent); - if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) - { - return $feed; - } - else - { - unset($array[$key]); - } - } - } - return null; - } - - function body(&$array) - { - foreach ($array as $key => $value) - { - if ($this->checked_feeds === $this->max_checked_feeds) - { - break; - } - if (preg_match('/(rss|rdf|atom|xml)/i', $value)) - { - $this->checked_feeds++; - $feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent); - if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed)) - { - return $feed; - } - else - { - unset($array[$key]); - } - } - } - return null; - } -} - -class SimplePie_Parser -{ - var $error_code; - var $error_string; - var $current_line; - var $current_column; - var $current_byte; - var $separator = ' '; - var $namespace = array(''); - var $element = array(''); - var $xml_base = array(''); - var $xml_base_explicit = array(false); - var $xml_lang = array(''); - var $data = array(); - var $datas = array(array()); - var $current_xhtml_construct = -1; - var $encoding; - - function parse(&$data, $encoding) - { - // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character - if (strtoupper($encoding) === 'US-ASCII') - { - $this->encoding = 'UTF-8'; - } - else - { - $this->encoding = $encoding; - } - - // Strip BOM: - // UTF-32 Big Endian BOM - if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") - { - $data = substr($data, 4); - } - // UTF-32 Little Endian BOM - elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") - { - $data = substr($data, 4); - } - // UTF-16 Big Endian BOM - elseif (substr($data, 0, 2) === "\xFE\xFF") - { - $data = substr($data, 2); - } - // UTF-16 Little Endian BOM - elseif (substr($data, 0, 2) === "\xFF\xFE") - { - $data = substr($data, 2); - } - // UTF-8 BOM - elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") - { - $data = substr($data, 3); - } - - if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) - { - $declaration = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5)); - if ($declaration->parse()) - { - $data = substr($data, $pos + 2); - $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; - } - else - { - $this->error_string = 'SimplePie bug! Please report this!'; - return false; - } - } - - $return = true; - - static $xml_is_sane = null; - if ($xml_is_sane === null) - { - $parser_check = xml_parser_create(); - xml_parse_into_struct($parser_check, '<foo>&</foo>', $values); - xml_parser_free($parser_check); - $xml_is_sane = isset($values[0]['value']); - } - - // Create the parser - if ($xml_is_sane) - { - $xml = xml_parser_create_ns($this->encoding, $this->separator); - xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0); - xml_set_object($xml, $this); - xml_set_character_data_handler($xml, 'cdata'); - xml_set_element_handler($xml, 'tag_open', 'tag_close'); - - // Parse! - if (!xml_parse($xml, $data, true)) - { - $this->error_code = xml_get_error_code($xml); - $this->error_string = xml_error_string($this->error_code); - $return = false; - } - $this->current_line = xml_get_current_line_number($xml); - $this->current_column = xml_get_current_column_number($xml); - $this->current_byte = xml_get_current_byte_index($xml); - xml_parser_free($xml); - return $return; - } - else - { - libxml_clear_errors(); - $xml = new XMLReader(); - $xml->xml($data); - while (@$xml->read()) - { - switch ($xml->nodeType) - { - - case constant('XMLReader::END_ELEMENT'): - if ($xml->namespaceURI !== '') - { - $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}"; - } - else - { - $tagName = $xml->localName; - } - $this->tag_close(null, $tagName); - break; - case constant('XMLReader::ELEMENT'): - $empty = $xml->isEmptyElement; - if ($xml->namespaceURI !== '') - { - $tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}"; - } - else - { - $tagName = $xml->localName; - } - $attributes = array(); - while ($xml->moveToNextAttribute()) - { - if ($xml->namespaceURI !== '') - { - $attrName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}"; - } - else - { - $attrName = $xml->localName; - } - $attributes[$attrName] = $xml->value; - } - $this->tag_open(null, $tagName, $attributes); - if ($empty) - { - $this->tag_close(null, $tagName); - } - break; - case constant('XMLReader::TEXT'): - - case constant('XMLReader::CDATA'): - $this->cdata(null, $xml->value); - break; - } - } - if ($error = libxml_get_last_error()) - { - $this->error_code = $error->code; - $this->error_string = $error->message; - $this->current_line = $error->line; - $this->current_column = $error->column; - return false; - } - else - { - return true; - } - } - } - - function get_error_code() - { - return $this->error_code; - } - - function get_error_string() - { - return $this->error_string; - } - - function get_current_line() - { - return $this->current_line; - } - - function get_current_column() - { - return $this->current_column; - } - - function get_current_byte() - { - return $this->current_byte; - } - - function get_data() - { - return $this->data; - } - - function tag_open($parser, $tag, $attributes) - { - list($this->namespace[], $this->element[]) = $this->split_ns($tag); - - $attribs = array(); - foreach ($attributes as $name => $value) - { - list($attrib_namespace, $attribute) = $this->split_ns($name); - $attribs[$attrib_namespace][$attribute] = $value; - } - - if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['base'])) - { - $this->xml_base[] = SimplePie_Misc::absolutize_url($attribs[SIMPLEPIE_NAMESPACE_XML]['base'], end($this->xml_base)); - $this->xml_base_explicit[] = true; - } - else - { - $this->xml_base[] = end($this->xml_base); - $this->xml_base_explicit[] = end($this->xml_base_explicit); - } - - if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['lang'])) - { - $this->xml_lang[] = $attribs[SIMPLEPIE_NAMESPACE_XML]['lang']; - } - else - { - $this->xml_lang[] = end($this->xml_lang); - } - - if ($this->current_xhtml_construct >= 0) - { - $this->current_xhtml_construct++; - if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML) - { - $this->data['data'] .= '<' . end($this->element); - if (isset($attribs[''])) - { - foreach ($attribs[''] as $name => $value) - { - $this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"'; - } - } - $this->data['data'] .= '>'; - } - } - else - { - $this->datas[] =& $this->data; - $this->data =& $this->data['child'][end($this->namespace)][end($this->element)][]; - $this->data = array('data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang)); - if ((end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_03 && in_array(end($this->element), array('title', 'tagline', 'copyright', 'info', 'summary', 'content')) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml') - || (end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_10 && in_array(end($this->element), array('rights', 'subtitle', 'summary', 'info', 'title', 'content')) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml')) - { - $this->current_xhtml_construct = 0; - } - } - } - - function cdata($parser, $cdata) - { - if ($this->current_xhtml_construct >= 0) - { - $this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding); - } - else - { - $this->data['data'] .= $cdata; - } - } - - function tag_close($parser, $tag) - { - if ($this->current_xhtml_construct >= 0) - { - $this->current_xhtml_construct--; - if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML && !in_array(end($this->element), array('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'))) - { - $this->data['data'] .= '</' . end($this->element) . '>'; - } - } - if ($this->current_xhtml_construct === -1) - { - $this->data =& $this->datas[count($this->datas) - 1]; - array_pop($this->datas); - } - - array_pop($this->element); - array_pop($this->namespace); - array_pop($this->xml_base); - array_pop($this->xml_base_explicit); - array_pop($this->xml_lang); - } - - function split_ns($string) - { - static $cache = array(); - if (!isset($cache[$string])) - { - if ($pos = strpos($string, $this->separator)) - { - static $separator_length; - if (!$separator_length) - { - $separator_length = strlen($this->separator); - } - $namespace = substr($string, 0, $pos); - $local_name = substr($string, $pos + $separator_length); - if (strtolower($namespace) === SIMPLEPIE_NAMESPACE_ITUNES) - { - $namespace = SIMPLEPIE_NAMESPACE_ITUNES; - } - - // Normalize the Media RSS namespaces - if ($namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG) - { - $namespace = SIMPLEPIE_NAMESPACE_MEDIARSS; - } - $cache[$string] = array($namespace, $local_name); - } - else - { - $cache[$string] = array('', $string); - } - } - return $cache[$string]; - } -} - -/** - * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags - */ -class SimplePie_Sanitize -{ - // Private vars - var $base; - - // Options - var $remove_div = true; - var $image_handler = ''; - var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); - var $encode_instead_of_strip = false; - var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); - var $strip_comments = false; - var $output_encoding = 'UTF-8'; - var $enable_cache = true; - var $cache_location = './cache'; - var $cache_name_function = 'md5'; - var $cache_class = 'SimplePie_Cache'; - var $file_class = 'SimplePie_File'; - var $timeout = 10; - var $useragent = ''; - var $force_fsockopen = false; - - var $replace_url_attributes = array( - 'a' => 'href', - 'area' => 'href', - 'blockquote' => 'cite', - 'del' => 'cite', - 'form' => 'action', - 'img' => array('longdesc', 'src'), - 'input' => 'src', - 'ins' => 'cite', - 'q' => 'cite' - ); - - function remove_div($enable = true) - { - $this->remove_div = (bool) $enable; - } - - function set_image_handler($page = false) - { - if ($page) - { - $this->image_handler = (string) $page; - } - else - { - $this->image_handler = false; - } - } - - function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie_Cache') - { - if (isset($enable_cache)) - { - $this->enable_cache = (bool) $enable_cache; - } - - if ($cache_location) - { - $this->cache_location = (string) $cache_location; - } - - if ($cache_name_function) - { - $this->cache_name_function = (string) $cache_name_function; - } - - if ($cache_class) - { - $this->cache_class = (string) $cache_class; - } - } - - function pass_file_data($file_class = 'SimplePie_File', $timeout = 10, $useragent = '', $force_fsockopen = false) - { - if ($file_class) - { - $this->file_class = (string) $file_class; - } - - if ($timeout) - { - $this->timeout = (string) $timeout; - } - - if ($useragent) - { - $this->useragent = (string) $useragent; - } - - if ($force_fsockopen) - { - $this->force_fsockopen = (string) $force_fsockopen; - } - } - - function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style')) - { - if ($tags) - { - if (is_array($tags)) - { - $this->strip_htmltags = $tags; - } - else - { - $this->strip_htmltags = explode(',', $tags); - } - } - else - { - $this->strip_htmltags = false; - } - } - - function encode_instead_of_strip($encode = false) - { - $this->encode_instead_of_strip = (bool) $encode; - } - - function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc')) - { - if ($attribs) - { - if (is_array($attribs)) - { - $this->strip_attributes = $attribs; - } - else - { - $this->strip_attributes = explode(',', $attribs); - } - } - else - { - $this->strip_attributes = false; - } - } - - function strip_comments($strip = false) - { - $this->strip_comments = (bool) $strip; - } - - function set_output_encoding($encoding = 'UTF-8') - { - $this->output_encoding = (string) $encoding; - } - - /** - * Set element/attribute key/value pairs of HTML attributes - * containing URLs that need to be resolved relative to the feed - * - * @access public - * @since 1.0 - * @param array $element_attribute Element/attribute key/value pairs - */ - function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite')) - { - $this->replace_url_attributes = (array) $element_attribute; - } - - function sanitize($data, $type, $base = '') - { - $data = trim($data); - if ($data !== '' || $type & SIMPLEPIE_CONSTRUCT_IRI) - { - if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML) - { - if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data)) - { - $type |= SIMPLEPIE_CONSTRUCT_HTML; - } - else - { - $type |= SIMPLEPIE_CONSTRUCT_TEXT; - } - } - - if ($type & SIMPLEPIE_CONSTRUCT_BASE64) - { - $data = base64_decode($data); - } - - if ($type & SIMPLEPIE_CONSTRUCT_XHTML) - { - if ($this->remove_div) - { - $data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '', $data); - $data = preg_replace('/<\/div>$/', '', $data); - } - else - { - $data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '<div>', $data); - } - } - - if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML)) - { - // Strip comments - if ($this->strip_comments) - { - $data = SimplePie_Misc::strip_comments($data); - } - - // Strip out HTML tags and attributes that might cause various security problems. - // Based on recommendations by Mark Pilgrim at: - // http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely - if ($this->strip_htmltags) - { - foreach ($this->strip_htmltags as $tag) - { - $pcre = "/<($tag)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$tag" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>|(\/)?>)/siU'; - while (preg_match($pcre, $data)) - { - $data = preg_replace_callback($pcre, array(&$this, 'do_strip_htmltags'), $data); - } - } - } - - if ($this->strip_attributes) - { - foreach ($this->strip_attributes as $attrib) - { - $data = preg_replace('/(<[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*)' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . trim($attrib) . '(?:\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>/', '\1\2\3>', $data); - } - } - - // Replace relative URLs - $this->base = $base; - foreach ($this->replace_url_attributes as $element => $attributes) - { - $data = $this->replace_urls($data, $element, $attributes); - } - - // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags. - if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache) - { - $images = SimplePie_Misc::get_element('img', $data); - foreach ($images as $img) - { - if (isset($img['attribs']['src']['data'])) - { - $image_url = call_user_func($this->cache_name_function, $img['attribs']['src']['data']); - $cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, $image_url, 'spi'); - - if ($cache->load()) - { - $img['attribs']['src']['data'] = $this->image_handler . $image_url; - $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); - } - else - { - $file = new $this->file_class($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen); - $headers = $file->headers; - - if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) - { - if ($cache->save(array('headers' => $file->headers, 'body' => $file->body))) - { - $img['attribs']['src']['data'] = $this->image_handler . $image_url; - $data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data); - } - else - { - trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); - } - } - } - } - } - } - - // Having (possibly) taken stuff out, there may now be whitespace at the beginning/end of the data - $data = trim($data); - } - - if ($type & SIMPLEPIE_CONSTRUCT_IRI) - { - $data = SimplePie_Misc::absolutize_url($data, $base); - } - - if ($type & (SIMPLEPIE_CONSTRUCT_TEXT | SIMPLEPIE_CONSTRUCT_IRI)) - { - $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8'); - } - - if ($this->output_encoding !== 'UTF-8') - { - $data = SimplePie_Misc::change_encoding($data, 'UTF-8', $this->output_encoding); - } - } - return $data; - } - - function replace_urls($data, $tag, $attributes) - { - if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) - { - $elements = SimplePie_Misc::get_element($tag, $data); - foreach ($elements as $element) - { - if (is_array($attributes)) - { - foreach ($attributes as $attribute) - { - if (isset($element['attribs'][$attribute]['data'])) - { - $element['attribs'][$attribute]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attribute]['data'], $this->base); - $new_element = SimplePie_Misc::element_implode($element); - $data = str_replace($element['full'], $new_element, $data); - $element['full'] = $new_element; - } - } - } - elseif (isset($element['attribs'][$attributes]['data'])) - { - $element['attribs'][$attributes]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attributes]['data'], $this->base); - $data = str_replace($element['full'], SimplePie_Misc::element_implode($element), $data); - } - } - } - return $data; - } - - function do_strip_htmltags($match) - { - if ($this->encode_instead_of_strip) - { - if (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) - { - $match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8'); - $match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8'); - return "<$match[1]$match[2]>$match[3]</$match[1]>"; - } - else - { - return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8'); - } - } - elseif (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style'))) - { - return $match[4]; - } - else - { - return ''; - } - } -} - -?> diff --git a/util/README b/util/README index 20a539215e..fad1270d47 100644 --- a/util/README +++ b/util/README @@ -10,7 +10,7 @@ Internationalisation extract.php - extracts translatable strings from our project files. It currently doesn't pick up strings in other libraries we might be using such as -tinymce, simplepie, and the HTML parsers. +tinymce and the HTML parsers. In order for extract to do its job, every use of the t() translation function must be preceded by one space. The string also can not contain parentheses. If From a18fde458c9b8d8d1b575de4420f301610e5ca9c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 16 Feb 2016 08:06:55 +0100 Subject: [PATCH 108/273] Added documentation. --- include/feed.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/include/feed.php b/include/feed.php index 184b784c72..04cfba75a6 100644 --- a/include/feed.php +++ b/include/feed.php @@ -2,6 +2,17 @@ require_once("include/html2bbcode.php"); require_once("include/items.php"); +/** + * @brief Read a RSS/RDF/Atom feed and create an item entry for it + * + * @param string $xml The feed data + * @param array $importer The user record of the importer + * @param array $contact The contact record of the feed + * @param string $hub Unused dummy value for compatibility reasons + * @param bool $simulate If enabled, no data is imported + * + * @return array In simulation mode it returns the header and the first item + */ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { $a = get_app(); @@ -102,7 +113,7 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { $entries = $xpath->query('/rss/channel/item'); } - if (is_array($contact)) { + if (!$simulate) { $author["author-link"] = $contact["url"]; if ($author["author-name"] == "") @@ -113,6 +124,9 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { $author["owner-link"] = $contact["url"]; $author["owner-name"] = $contact["name"]; $author["owner-avatar"] = $contact["thumb"]; + + // This is no field in the item table. So we have to unset it. + unset($author["author-nick"]); } $header = array(); From a9f80d435b475f4f23414b06ecc2b6af591dd1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Saka=C5=82o=C5=AD=20Alaksiej?= <nullbsd@gmail.com> Date: Tue, 16 Feb 2016 20:57:15 +0300 Subject: [PATCH 109/273] Complemented and improved russian translation --- view/ru/messages.po | 6800 +++++++++++++++++++++++++------------------ view/ru/strings.php | 637 ++-- 2 files changed, 4326 insertions(+), 3111 deletions(-) diff --git a/view/ru/messages.po b/view/ru/messages.po index 4b0e372dd6..7f65e8f1be 100644 --- a/view/ru/messages.po +++ b/view/ru/messages.po @@ -1,8 +1,9 @@ # FRIENDICA Distributed Social Network # Copyright (C) 2010, 2011, 2012, 2013 the Friendica Project # This file is distributed under the same license as the Friendica package. -# +# # Translators: +# Aliaksei Sakalou <nullbsd@gmail.com>, 2016 # Alex <info@pixelbits.de>, 2013 # vislav <bizadmin@list.ru>, 2014 # Alex <info@pixelbits.de>, 2013 @@ -15,1151 +16,1317 @@ msgid "" msgstr "" "Project-Id-Version: friendica\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-09 08:57+0100\n" -"PO-Revision-Date: 2015-02-09 09:46+0000\n" -"Last-Translator: fabrixxm <fabrix.xm@gmail.com>\n" -"Language-Team: Russian (http://www.transifex.com/projects/p/friendica/language/ru/)\n" +"POT-Creation-Date: 2016-01-24 06:49+0100\n" +"PO-Revision-Date: 2016-02-16 18:28+0300\n" +"Last-Translator: Aliaksei Sakalou <nullbsd@gmail.com>\n" +"Language-Team: Russian (http://www.transifex.com/Friendica/friendica/" +"language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" +"%100>=11 && n%100<=14)? 2 : 3);\n" +"X-Generator: Poedit 1.5.4\n" -#: ../../mod/contacts.php:108 +#: mod/contacts.php:50 include/identity.php:395 +msgid "Network:" +msgstr "Сеть:" + +#: mod/contacts.php:51 mod/contacts.php:961 mod/videos.php:37 +#: mod/viewcontacts.php:105 mod/dirfind.php:214 mod/network.php:598 +#: mod/allfriends.php:77 mod/match.php:82 mod/directory.php:172 +#: mod/common.php:123 mod/suggest.php:95 mod/photos.php:41 +#: include/identity.php:298 +msgid "Forum" +msgstr "Форум" + +#: mod/contacts.php:128 #, php-format msgid "%d contact edited." -msgid_plural "%d contacts edited" -msgstr[0] "%d контакт изменён." -msgstr[1] "%d контакты изменены" -msgstr[2] "%d контакты изменены" +msgid_plural "%d contacts edited." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" -#: ../../mod/contacts.php:139 ../../mod/contacts.php:272 +#: mod/contacts.php:159 mod/contacts.php:383 msgid "Could not access contact record." msgstr "Не удалось получить доступ к записи контакта." -#: ../../mod/contacts.php:153 +#: mod/contacts.php:173 msgid "Could not locate selected profile." msgstr "Не удалось найти выбранный профиль." -#: ../../mod/contacts.php:186 +#: mod/contacts.php:206 msgid "Contact updated." msgstr "Контакт обновлен." -#: ../../mod/contacts.php:188 ../../mod/dfrn_request.php:576 +#: mod/contacts.php:208 mod/dfrn_request.php:575 msgid "Failed to update contact record." msgstr "Не удалось обновить запись контакта." -#: ../../mod/contacts.php:254 ../../mod/manage.php:96 -#: ../../mod/display.php:499 ../../mod/profile_photo.php:19 -#: ../../mod/profile_photo.php:169 ../../mod/profile_photo.php:180 -#: ../../mod/profile_photo.php:193 ../../mod/follow.php:9 -#: ../../mod/item.php:168 ../../mod/item.php:184 ../../mod/group.php:19 -#: ../../mod/dfrn_confirm.php:55 ../../mod/fsuggest.php:78 -#: ../../mod/wall_upload.php:66 ../../mod/viewcontacts.php:24 -#: ../../mod/notifications.php:66 ../../mod/message.php:38 -#: ../../mod/message.php:174 ../../mod/crepair.php:119 -#: ../../mod/nogroup.php:25 ../../mod/network.php:4 ../../mod/allfriends.php:9 -#: ../../mod/events.php:140 ../../mod/install.php:151 -#: ../../mod/wallmessage.php:9 ../../mod/wallmessage.php:33 -#: ../../mod/wallmessage.php:79 ../../mod/wallmessage.php:103 -#: ../../mod/wall_attach.php:55 ../../mod/settings.php:102 -#: ../../mod/settings.php:596 ../../mod/settings.php:601 -#: ../../mod/register.php:42 ../../mod/delegate.php:12 ../../mod/mood.php:114 -#: ../../mod/suggest.php:58 ../../mod/profiles.php:165 -#: ../../mod/profiles.php:618 ../../mod/editpost.php:10 ../../mod/api.php:26 -#: ../../mod/api.php:31 ../../mod/notes.php:20 ../../mod/poke.php:135 -#: ../../mod/invite.php:15 ../../mod/invite.php:101 ../../mod/photos.php:134 -#: ../../mod/photos.php:1050 ../../mod/regmod.php:110 ../../mod/uimport.php:23 -#: ../../mod/attach.php:33 ../../include/items.php:4712 ../../index.php:369 +#: mod/contacts.php:365 mod/manage.php:96 mod/display.php:509 +#: mod/profile_photo.php:19 mod/profile_photo.php:175 +#: mod/profile_photo.php:186 mod/profile_photo.php:199 +#: mod/ostatus_subscribe.php:9 mod/follow.php:11 mod/follow.php:73 +#: mod/follow.php:155 mod/item.php:180 mod/item.php:192 mod/group.php:19 +#: mod/dfrn_confirm.php:55 mod/fsuggest.php:78 mod/wall_upload.php:77 +#: mod/wall_upload.php:80 mod/viewcontacts.php:40 mod/notifications.php:69 +#: mod/message.php:45 mod/message.php:181 mod/crepair.php:117 +#: mod/dirfind.php:11 mod/nogroup.php:25 mod/network.php:4 +#: mod/allfriends.php:12 mod/events.php:165 mod/wallmessage.php:9 +#: mod/wallmessage.php:33 mod/wallmessage.php:79 mod/wallmessage.php:103 +#: mod/wall_attach.php:67 mod/wall_attach.php:70 mod/settings.php:20 +#: mod/settings.php:126 mod/settings.php:646 mod/register.php:42 +#: mod/delegate.php:12 mod/common.php:18 mod/mood.php:114 mod/suggest.php:58 +#: mod/profiles.php:165 mod/profiles.php:615 mod/editpost.php:10 +#: mod/api.php:26 mod/api.php:31 mod/notes.php:22 mod/poke.php:149 +#: mod/repair_ostatus.php:9 mod/invite.php:15 mod/invite.php:101 +#: mod/photos.php:171 mod/photos.php:1105 mod/regmod.php:110 +#: mod/uimport.php:23 mod/attach.php:33 include/items.php:5096 index.php:383 msgid "Permission denied." msgstr "Нет разрешения." -#: ../../mod/contacts.php:287 +#: mod/contacts.php:404 msgid "Contact has been blocked" msgstr "Контакт заблокирован" -#: ../../mod/contacts.php:287 +#: mod/contacts.php:404 msgid "Contact has been unblocked" msgstr "Контакт разблокирован" -#: ../../mod/contacts.php:298 +#: mod/contacts.php:415 msgid "Contact has been ignored" msgstr "Контакт проигнорирован" -#: ../../mod/contacts.php:298 +#: mod/contacts.php:415 msgid "Contact has been unignored" msgstr "У контакта отменено игнорирование" -#: ../../mod/contacts.php:310 +#: mod/contacts.php:427 msgid "Contact has been archived" msgstr "Контакт заархивирован" -#: ../../mod/contacts.php:310 +#: mod/contacts.php:427 msgid "Contact has been unarchived" msgstr "Контакт разархивирован" -#: ../../mod/contacts.php:335 ../../mod/contacts.php:711 +#: mod/contacts.php:454 mod/contacts.php:802 msgid "Do you really want to delete this contact?" msgstr "Вы действительно хотите удалить этот контакт?" -#: ../../mod/contacts.php:337 ../../mod/message.php:209 -#: ../../mod/settings.php:1010 ../../mod/settings.php:1016 -#: ../../mod/settings.php:1024 ../../mod/settings.php:1028 -#: ../../mod/settings.php:1033 ../../mod/settings.php:1039 -#: ../../mod/settings.php:1045 ../../mod/settings.php:1051 -#: ../../mod/settings.php:1081 ../../mod/settings.php:1082 -#: ../../mod/settings.php:1083 ../../mod/settings.php:1084 -#: ../../mod/settings.php:1085 ../../mod/dfrn_request.php:830 -#: ../../mod/register.php:233 ../../mod/suggest.php:29 -#: ../../mod/profiles.php:661 ../../mod/profiles.php:664 ../../mod/api.php:105 -#: ../../include/items.php:4557 +#: mod/contacts.php:456 mod/follow.php:110 mod/message.php:216 +#: mod/settings.php:1103 mod/settings.php:1109 mod/settings.php:1117 +#: mod/settings.php:1121 mod/settings.php:1126 mod/settings.php:1132 +#: mod/settings.php:1138 mod/settings.php:1144 mod/settings.php:1170 +#: mod/settings.php:1171 mod/settings.php:1172 mod/settings.php:1173 +#: mod/settings.php:1174 mod/dfrn_request.php:857 mod/register.php:238 +#: mod/suggest.php:29 mod/profiles.php:658 mod/profiles.php:661 +#: mod/profiles.php:687 mod/api.php:105 include/items.php:4928 msgid "Yes" msgstr "Да" -#: ../../mod/contacts.php:340 ../../mod/tagrm.php:11 ../../mod/tagrm.php:94 -#: ../../mod/message.php:212 ../../mod/fbrowser.php:81 -#: ../../mod/fbrowser.php:116 ../../mod/settings.php:615 -#: ../../mod/settings.php:641 ../../mod/dfrn_request.php:844 -#: ../../mod/suggest.php:32 ../../mod/editpost.php:148 -#: ../../mod/photos.php:203 ../../mod/photos.php:292 -#: ../../include/conversation.php:1129 ../../include/items.php:4560 +#: mod/contacts.php:459 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121 +#: mod/videos.php:131 mod/message.php:219 mod/fbrowser.php:93 +#: mod/fbrowser.php:128 mod/settings.php:660 mod/settings.php:686 +#: mod/dfrn_request.php:871 mod/suggest.php:32 mod/editpost.php:148 +#: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1220 +#: include/items.php:4931 msgid "Cancel" msgstr "Отмена" -#: ../../mod/contacts.php:352 +#: mod/contacts.php:471 msgid "Contact has been removed." msgstr "Контакт удален." -#: ../../mod/contacts.php:390 +#: mod/contacts.php:512 #, php-format msgid "You are mutual friends with %s" msgstr "У Вас взаимная дружба с %s" -#: ../../mod/contacts.php:394 +#: mod/contacts.php:516 #, php-format msgid "You are sharing with %s" msgstr "Вы делитесь с %s" -#: ../../mod/contacts.php:399 +#: mod/contacts.php:521 #, php-format msgid "%s is sharing with you" msgstr "%s делитса с Вами" -#: ../../mod/contacts.php:416 +#: mod/contacts.php:541 msgid "Private communications are not available for this contact." msgstr "Личные коммуникации недоступны для этого контакта." -#: ../../mod/contacts.php:419 ../../mod/admin.php:569 +#: mod/contacts.php:544 mod/admin.php:822 msgid "Never" msgstr "Никогда" -#: ../../mod/contacts.php:423 +#: mod/contacts.php:548 msgid "(Update was successful)" msgstr "(Обновление было успешно)" -#: ../../mod/contacts.php:423 +#: mod/contacts.php:548 msgid "(Update was not successful)" msgstr "(Обновление не удалось)" -#: ../../mod/contacts.php:425 +#: mod/contacts.php:550 msgid "Suggest friends" msgstr "Предложить друзей" -#: ../../mod/contacts.php:429 +#: mod/contacts.php:554 #, php-format msgid "Network type: %s" msgstr "Сеть: %s" -#: ../../mod/contacts.php:432 ../../include/contact_widgets.php:200 -#, php-format -msgid "%d contact in common" -msgid_plural "%d contacts in common" -msgstr[0] "%d Контакт" -msgstr[1] "%d Контактов" -msgstr[2] "%d Контактов" - -#: ../../mod/contacts.php:437 -msgid "View all contacts" -msgstr "Показать все контакты" - -#: ../../mod/contacts.php:442 ../../mod/contacts.php:501 -#: ../../mod/contacts.php:714 ../../mod/admin.php:1009 -msgid "Unblock" -msgstr "Разблокировать" - -#: ../../mod/contacts.php:442 ../../mod/contacts.php:501 -#: ../../mod/contacts.php:714 ../../mod/admin.php:1008 -msgid "Block" -msgstr "Заблокировать" - -#: ../../mod/contacts.php:445 -msgid "Toggle Blocked status" -msgstr "Изменить статус блокированности (заблокировать/разблокировать)" - -#: ../../mod/contacts.php:448 ../../mod/contacts.php:502 -#: ../../mod/contacts.php:715 -msgid "Unignore" -msgstr "Не игнорировать" - -#: ../../mod/contacts.php:448 ../../mod/contacts.php:502 -#: ../../mod/contacts.php:715 ../../mod/notifications.php:51 -#: ../../mod/notifications.php:164 ../../mod/notifications.php:210 -msgid "Ignore" -msgstr "Игнорировать" - -#: ../../mod/contacts.php:451 -msgid "Toggle Ignored status" -msgstr "Изменить статус игнорирования" - -#: ../../mod/contacts.php:455 ../../mod/contacts.php:716 -msgid "Unarchive" -msgstr "Разархивировать" - -#: ../../mod/contacts.php:455 ../../mod/contacts.php:716 -msgid "Archive" -msgstr "Архивировать" - -#: ../../mod/contacts.php:458 -msgid "Toggle Archive status" -msgstr "Сменить статус архивации (архивирова/не архивировать)" - -#: ../../mod/contacts.php:461 -msgid "Repair" -msgstr "Восстановить" - -#: ../../mod/contacts.php:464 -msgid "Advanced Contact Settings" -msgstr "Дополнительные Настройки Контакта" - -#: ../../mod/contacts.php:470 +#: mod/contacts.php:567 msgid "Communications lost with this contact!" msgstr "Связь с контактом утеряна!" -#: ../../mod/contacts.php:473 -msgid "Contact Editor" -msgstr "Редактор контакта" +#: mod/contacts.php:570 +msgid "Fetch further information for feeds" +msgstr "" -#: ../../mod/contacts.php:475 ../../mod/manage.php:110 -#: ../../mod/fsuggest.php:107 ../../mod/message.php:335 -#: ../../mod/message.php:564 ../../mod/crepair.php:186 -#: ../../mod/events.php:478 ../../mod/content.php:710 -#: ../../mod/install.php:248 ../../mod/install.php:286 ../../mod/mood.php:137 -#: ../../mod/profiles.php:686 ../../mod/localtime.php:45 -#: ../../mod/poke.php:199 ../../mod/invite.php:140 ../../mod/photos.php:1084 -#: ../../mod/photos.php:1203 ../../mod/photos.php:1514 -#: ../../mod/photos.php:1565 ../../mod/photos.php:1609 -#: ../../mod/photos.php:1697 ../../object/Item.php:678 -#: ../../view/theme/cleanzero/config.php:80 -#: ../../view/theme/dispy/config.php:70 ../../view/theme/quattro/config.php:64 -#: ../../view/theme/diabook/config.php:148 -#: ../../view/theme/diabook/theme.php:633 ../../view/theme/vier/config.php:53 -#: ../../view/theme/duepuntozero/config.php:59 +#: mod/contacts.php:571 mod/admin.php:831 +msgid "Disabled" +msgstr "Отключенный" + +#: mod/contacts.php:571 +msgid "Fetch information" +msgstr "" + +#: mod/contacts.php:571 +msgid "Fetch information and keywords" +msgstr "" + +#: mod/contacts.php:587 mod/manage.php:143 mod/fsuggest.php:107 +#: mod/message.php:342 mod/message.php:525 mod/crepair.php:196 +#: mod/events.php:574 mod/content.php:712 mod/install.php:261 +#: mod/install.php:299 mod/mood.php:137 mod/profiles.php:696 +#: mod/localtime.php:45 mod/poke.php:198 mod/invite.php:140 +#: mod/photos.php:1137 mod/photos.php:1261 mod/photos.php:1579 +#: mod/photos.php:1630 mod/photos.php:1678 mod/photos.php:1766 +#: object/Item.php:710 view/theme/cleanzero/config.php:80 +#: view/theme/dispy/config.php:70 view/theme/quattro/config.php:64 +#: view/theme/diabook/config.php:148 view/theme/diabook/theme.php:633 +#: view/theme/vier/config.php:107 view/theme/duepuntozero/config.php:59 msgid "Submit" -msgstr "Подтвердить" +msgstr "Добавить" -#: ../../mod/contacts.php:476 +#: mod/contacts.php:588 msgid "Profile Visibility" msgstr "Видимость профиля" -#: ../../mod/contacts.php:477 +#: mod/contacts.php:589 #, php-format msgid "" "Please choose the profile you would like to display to %s when viewing your " "profile securely." -msgstr "Пожалуйста, выберите профиль, который вы хотите отображать %s, когда просмотр вашего профиля безопасен." +msgstr "" +"Пожалуйста, выберите профиль, который вы хотите отображать %s, когда " +"просмотр вашего профиля безопасен." -#: ../../mod/contacts.php:478 +#: mod/contacts.php:590 msgid "Contact Information / Notes" msgstr "Информация о контакте / Заметки" -#: ../../mod/contacts.php:479 +#: mod/contacts.php:591 msgid "Edit contact notes" msgstr "Редактировать заметки контакта" -#: ../../mod/contacts.php:484 ../../mod/contacts.php:679 -#: ../../mod/viewcontacts.php:64 ../../mod/nogroup.php:40 +#: mod/contacts.php:596 mod/contacts.php:952 mod/viewcontacts.php:97 +#: mod/nogroup.php:41 #, php-format msgid "Visit %s's profile [%s]" msgstr "Посетить профиль %s [%s]" -#: ../../mod/contacts.php:485 +#: mod/contacts.php:597 msgid "Block/Unblock contact" msgstr "Блокировать / Разблокировать контакт" -#: ../../mod/contacts.php:486 +#: mod/contacts.php:598 msgid "Ignore contact" msgstr "Игнорировать контакт" -#: ../../mod/contacts.php:487 +#: mod/contacts.php:599 msgid "Repair URL settings" msgstr "Восстановить настройки URL" -#: ../../mod/contacts.php:488 +#: mod/contacts.php:600 msgid "View conversations" msgstr "Просмотр бесед" -#: ../../mod/contacts.php:490 +#: mod/contacts.php:602 msgid "Delete contact" msgstr "Удалить контакт" -#: ../../mod/contacts.php:494 +#: mod/contacts.php:606 msgid "Last update:" msgstr "Последнее обновление: " -#: ../../mod/contacts.php:496 +#: mod/contacts.php:608 msgid "Update public posts" msgstr "Обновить публичные сообщения" -#: ../../mod/contacts.php:498 ../../mod/admin.php:1503 +#: mod/contacts.php:610 msgid "Update now" msgstr "Обновить сейчас" -#: ../../mod/contacts.php:505 +#: mod/contacts.php:612 mod/follow.php:103 mod/dirfind.php:196 +#: mod/allfriends.php:65 mod/match.php:71 mod/suggest.php:82 +#: include/contact_widgets.php:32 include/Contact.php:297 +#: include/conversation.php:924 +msgid "Connect/Follow" +msgstr "Подключиться/Следовать" + +#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865 +#: mod/admin.php:1312 +msgid "Unblock" +msgstr "Разблокировать" + +#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865 +#: mod/admin.php:1311 +msgid "Block" +msgstr "Заблокировать" + +#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872 +msgid "Unignore" +msgstr "Не игнорировать" + +#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872 +#: mod/notifications.php:54 mod/notifications.php:179 +#: mod/notifications.php:259 +msgid "Ignore" +msgstr "Игнорировать" + +#: mod/contacts.php:619 msgid "Currently blocked" msgstr "В настоящее время заблокирован" -#: ../../mod/contacts.php:506 +#: mod/contacts.php:620 msgid "Currently ignored" msgstr "В настоящее время игнорируется" -#: ../../mod/contacts.php:507 +#: mod/contacts.php:621 msgid "Currently archived" msgstr "В данный момент архивирован" -#: ../../mod/contacts.php:508 ../../mod/notifications.php:157 -#: ../../mod/notifications.php:204 +#: mod/contacts.php:622 mod/notifications.php:172 mod/notifications.php:251 msgid "Hide this contact from others" msgstr "Скрыть этот контакт от других" -#: ../../mod/contacts.php:508 +#: mod/contacts.php:622 msgid "" "Replies/likes to your public posts <strong>may</strong> still be visible" msgstr "Ответы/лайки ваших публичных сообщений <strong>будут</strong> видимы." -#: ../../mod/contacts.php:509 +#: mod/contacts.php:623 msgid "Notification for new posts" msgstr "" -#: ../../mod/contacts.php:509 +#: mod/contacts.php:623 msgid "Send a notification of every new post of this contact" msgstr "" -#: ../../mod/contacts.php:510 -msgid "Fetch further information for feeds" -msgstr "" - -#: ../../mod/contacts.php:511 -msgid "Disabled" -msgstr "" - -#: ../../mod/contacts.php:511 -msgid "Fetch information" -msgstr "" - -#: ../../mod/contacts.php:511 -msgid "Fetch information and keywords" -msgstr "" - -#: ../../mod/contacts.php:513 +#: mod/contacts.php:626 msgid "Blacklisted keywords" msgstr "" -#: ../../mod/contacts.php:513 +#: mod/contacts.php:626 msgid "" "Comma separated list of keywords that should not be converted to hashtags, " "when \"Fetch information and keywords\" is selected" msgstr "" -#: ../../mod/contacts.php:564 +#: mod/contacts.php:633 mod/follow.php:126 mod/notifications.php:255 +msgid "Profile URL" +msgstr "URL профиля" + +#: mod/contacts.php:636 mod/notifications.php:244 mod/events.php:566 +#: mod/directory.php:145 include/identity.php:308 include/bb2diaspora.php:170 +#: include/event.php:36 include/event.php:60 +msgid "Location:" +msgstr "Откуда:" + +#: mod/contacts.php:638 mod/notifications.php:246 mod/directory.php:153 +#: include/identity.php:317 include/identity.php:631 +msgid "About:" +msgstr "О себе:" + +#: mod/contacts.php:640 mod/follow.php:134 mod/notifications.php:248 +#: include/identity.php:625 +msgid "Tags:" +msgstr "Ключевые слова: " + +#: mod/contacts.php:685 msgid "Suggestions" msgstr "Предложения" -#: ../../mod/contacts.php:567 +#: mod/contacts.php:688 msgid "Suggest potential friends" msgstr "Предложить потенциального знакомого" -#: ../../mod/contacts.php:570 ../../mod/group.php:194 +#: mod/contacts.php:693 mod/group.php:192 msgid "All Contacts" msgstr "Все контакты" -#: ../../mod/contacts.php:573 +#: mod/contacts.php:696 msgid "Show all contacts" msgstr "Показать все контакты" -#: ../../mod/contacts.php:576 +#: mod/contacts.php:701 msgid "Unblocked" msgstr "Не блокирован" -#: ../../mod/contacts.php:579 +#: mod/contacts.php:704 msgid "Only show unblocked contacts" msgstr "Показать только не блокированные контакты" -#: ../../mod/contacts.php:583 +#: mod/contacts.php:710 msgid "Blocked" msgstr "Заблокирован" -#: ../../mod/contacts.php:586 +#: mod/contacts.php:713 msgid "Only show blocked contacts" msgstr "Показать только блокированные контакты" -#: ../../mod/contacts.php:590 +#: mod/contacts.php:719 msgid "Ignored" msgstr "Игнорирован" -#: ../../mod/contacts.php:593 +#: mod/contacts.php:722 msgid "Only show ignored contacts" msgstr "Показать только игнорируемые контакты" -#: ../../mod/contacts.php:597 +#: mod/contacts.php:728 msgid "Archived" msgstr "Архивированные" -#: ../../mod/contacts.php:600 +#: mod/contacts.php:731 msgid "Only show archived contacts" msgstr "Показывать только архивные контакты" -#: ../../mod/contacts.php:604 +#: mod/contacts.php:737 msgid "Hidden" msgstr "Скрытые" -#: ../../mod/contacts.php:607 +#: mod/contacts.php:740 msgid "Only show hidden contacts" msgstr "Показывать только скрытые контакты" -#: ../../mod/contacts.php:655 -msgid "Mutual Friendship" -msgstr "Взаимная дружба" - -#: ../../mod/contacts.php:659 -msgid "is a fan of yours" -msgstr "является вашим поклонником" - -#: ../../mod/contacts.php:663 -msgid "you are a fan of" -msgstr "Вы - поклонник" - -#: ../../mod/contacts.php:680 ../../mod/nogroup.php:41 -msgid "Edit contact" -msgstr "Редактировать контакт" - -#: ../../mod/contacts.php:702 ../../include/nav.php:177 -#: ../../view/theme/diabook/theme.php:125 +#: mod/contacts.php:793 mod/contacts.php:841 mod/viewcontacts.php:116 +#: include/identity.php:741 include/identity.php:744 include/text.php:1012 +#: include/nav.php:123 include/nav.php:187 view/theme/diabook/theme.php:125 msgid "Contacts" msgstr "Контакты" -#: ../../mod/contacts.php:706 +#: mod/contacts.php:797 msgid "Search your contacts" msgstr "Поиск ваших контактов" -#: ../../mod/contacts.php:707 ../../mod/directory.php:61 +#: mod/contacts.php:798 msgid "Finding: " msgstr "Результат поиска: " -#: ../../mod/contacts.php:708 ../../mod/directory.php:63 -#: ../../include/contact_widgets.php:34 +#: mod/contacts.php:799 mod/directory.php:210 include/contact_widgets.php:34 msgid "Find" msgstr "Найти" -#: ../../mod/contacts.php:713 ../../mod/settings.php:132 -#: ../../mod/settings.php:640 +#: mod/contacts.php:805 mod/settings.php:156 mod/settings.php:685 msgid "Update" msgstr "Обновление" -#: ../../mod/contacts.php:717 ../../mod/group.php:171 ../../mod/admin.php:1007 -#: ../../mod/content.php:438 ../../mod/content.php:741 -#: ../../mod/settings.php:677 ../../mod/photos.php:1654 -#: ../../object/Item.php:130 ../../include/conversation.php:614 +#: mod/contacts.php:808 mod/contacts.php:879 +msgid "Archive" +msgstr "Архивировать" + +#: mod/contacts.php:808 mod/contacts.php:879 +msgid "Unarchive" +msgstr "Разархивировать" + +#: mod/contacts.php:809 mod/group.php:171 mod/admin.php:1310 +#: mod/content.php:440 mod/content.php:743 mod/settings.php:722 +#: mod/photos.php:1723 object/Item.php:134 include/conversation.php:635 msgid "Delete" msgstr "Удалить" -#: ../../mod/hcard.php:10 +#: mod/contacts.php:822 include/identity.php:686 include/nav.php:75 +msgid "Status" +msgstr "Посты" + +#: mod/contacts.php:825 mod/follow.php:143 include/identity.php:689 +msgid "Status Messages and Posts" +msgstr "Ваши посты" + +#: mod/contacts.php:830 mod/profperm.php:104 mod/newmember.php:32 +#: include/identity.php:579 include/identity.php:665 include/identity.php:694 +#: include/nav.php:76 view/theme/diabook/theme.php:124 +msgid "Profile" +msgstr "Информация" + +#: mod/contacts.php:833 include/identity.php:697 +msgid "Profile Details" +msgstr "Информация о вас" + +#: mod/contacts.php:844 +msgid "View all contacts" +msgstr "Показать все контакты" + +#: mod/contacts.php:850 mod/common.php:134 +msgid "Common Friends" +msgstr "Общие друзья" + +#: mod/contacts.php:853 +msgid "View all common friends" +msgstr "" + +#: mod/contacts.php:857 +msgid "Repair" +msgstr "Восстановить" + +#: mod/contacts.php:860 +msgid "Advanced Contact Settings" +msgstr "Дополнительные Настройки Контакта" + +#: mod/contacts.php:868 +msgid "Toggle Blocked status" +msgstr "Изменить статус блокированности (заблокировать/разблокировать)" + +#: mod/contacts.php:875 +msgid "Toggle Ignored status" +msgstr "Изменить статус игнорирования" + +#: mod/contacts.php:882 +msgid "Toggle Archive status" +msgstr "Сменить статус архивации (архивирова/не архивировать)" + +#: mod/contacts.php:924 +msgid "Mutual Friendship" +msgstr "Взаимная дружба" + +#: mod/contacts.php:928 +msgid "is a fan of yours" +msgstr "является вашим поклонником" + +#: mod/contacts.php:932 +msgid "you are a fan of" +msgstr "Вы - поклонник" + +#: mod/contacts.php:953 mod/nogroup.php:42 +msgid "Edit contact" +msgstr "Редактировать контакт" + +#: mod/hcard.php:10 msgid "No profile" msgstr "Нет профиля" -#: ../../mod/manage.php:106 +#: mod/manage.php:139 msgid "Manage Identities and/or Pages" msgstr "Управление идентификацией и / или страницами" -#: ../../mod/manage.php:107 +#: mod/manage.php:140 msgid "" "Toggle between different identities or community/group pages which share " "your account details or which you have been granted \"manage\" permissions" msgstr "" -#: ../../mod/manage.php:108 +#: mod/manage.php:141 msgid "Select an identity to manage: " msgstr "Выберите идентификацию для управления: " -#: ../../mod/oexchange.php:25 +#: mod/oexchange.php:25 msgid "Post successful." msgstr "Успешно добавлено." -#: ../../mod/profperm.php:19 ../../mod/group.php:72 ../../index.php:368 +#: mod/profperm.php:19 mod/group.php:72 index.php:382 msgid "Permission denied" msgstr "Доступ запрещен" -#: ../../mod/profperm.php:25 ../../mod/profperm.php:55 +#: mod/profperm.php:25 mod/profperm.php:56 msgid "Invalid profile identifier." msgstr "Недопустимый идентификатор профиля." -#: ../../mod/profperm.php:101 +#: mod/profperm.php:102 msgid "Profile Visibility Editor" msgstr "Редактор видимости профиля" -#: ../../mod/profperm.php:103 ../../mod/newmember.php:32 ../../boot.php:2119 -#: ../../include/profile_advanced.php:7 ../../include/profile_advanced.php:87 -#: ../../include/nav.php:77 ../../view/theme/diabook/theme.php:124 -msgid "Profile" -msgstr "Профиль" - -#: ../../mod/profperm.php:105 ../../mod/group.php:224 +#: mod/profperm.php:106 mod/group.php:223 msgid "Click on a contact to add or remove." msgstr "Нажмите на контакт, чтобы добавить или удалить." -#: ../../mod/profperm.php:114 +#: mod/profperm.php:115 msgid "Visible To" msgstr "Видимый для" -#: ../../mod/profperm.php:130 +#: mod/profperm.php:131 msgid "All Contacts (with secure profile access)" msgstr "Все контакты (с безопасным доступом к профилю)" -#: ../../mod/display.php:82 ../../mod/display.php:284 -#: ../../mod/display.php:503 ../../mod/viewsrc.php:15 ../../mod/admin.php:169 -#: ../../mod/admin.php:1052 ../../mod/admin.php:1265 ../../mod/notice.php:15 -#: ../../include/items.php:4516 +#: mod/display.php:82 mod/display.php:291 mod/display.php:513 +#: mod/viewsrc.php:15 mod/admin.php:234 mod/admin.php:1365 mod/admin.php:1599 +#: mod/notice.php:15 include/items.php:4887 msgid "Item not found." msgstr "Пункт не найден." -#: ../../mod/display.php:212 ../../mod/videos.php:115 -#: ../../mod/viewcontacts.php:19 ../../mod/community.php:18 -#: ../../mod/dfrn_request.php:762 ../../mod/search.php:89 -#: ../../mod/directory.php:33 ../../mod/photos.php:920 +#: mod/display.php:220 mod/videos.php:197 mod/viewcontacts.php:35 +#: mod/community.php:22 mod/dfrn_request.php:786 mod/search.php:93 +#: mod/search.php:99 mod/directory.php:37 mod/photos.php:976 msgid "Public access denied." msgstr "Свободный доступ закрыт." -#: ../../mod/display.php:332 ../../mod/profile.php:155 +#: mod/display.php:339 mod/profile.php:155 msgid "Access to this profile has been restricted." msgstr "Доступ к этому профилю ограничен." -#: ../../mod/display.php:496 +#: mod/display.php:506 msgid "Item has been removed." msgstr "Пункт был удален." -#: ../../mod/newmember.php:6 +#: mod/newmember.php:6 msgid "Welcome to Friendica" msgstr "Добро пожаловать в Friendica" -#: ../../mod/newmember.php:8 +#: mod/newmember.php:8 msgid "New Member Checklist" msgstr "Новый контрольный список участников" -#: ../../mod/newmember.php:12 +#: mod/newmember.php:12 msgid "" "We would like to offer some tips and links to help make your experience " "enjoyable. Click any item to visit the relevant page. A link to this page " "will be visible from your home page for two weeks after your initial " "registration and then will quietly disappear." -msgstr "Мы хотели бы предложить некоторые советы и ссылки, помогающие сделать вашу работу приятнее. Нажмите на любой элемент, чтобы посетить соответствующую страницу. Ссылка на эту страницу будет видна на вашей домашней странице в течение двух недель после первоначальной регистрации, а затем она исчезнет." +msgstr "" +"Мы хотели бы предложить некоторые советы и ссылки, помогающие сделать вашу " +"работу приятнее. Нажмите на любой элемент, чтобы посетить соответствующую " +"страницу. Ссылка на эту страницу будет видна на вашей домашней странице в " +"течение двух недель после первоначальной регистрации, а затем она исчезнет." -#: ../../mod/newmember.php:14 +#: mod/newmember.php:14 msgid "Getting Started" msgstr "Начало работы" -#: ../../mod/newmember.php:18 +#: mod/newmember.php:18 msgid "Friendica Walk-Through" msgstr "Friendica тур" -#: ../../mod/newmember.php:18 +#: mod/newmember.php:18 msgid "" "On your <em>Quick Start</em> page - find a brief introduction to your " -"profile and network tabs, make some new connections, and find some groups to" -" join." -msgstr "На вашей странице <em>Быстрый старт</em> - можно найти краткое введение в ваш профиль и сетевые закладки, создать новые связи, и найти группы, чтобы присоединиться к ним." +"profile and network tabs, make some new connections, and find some groups to " +"join." +msgstr "" +"На вашей странице <em>Быстрый старт</em> - можно найти краткое введение в " +"ваш профиль и сетевые закладки, создать новые связи, и найти группы, чтобы " +"присоединиться к ним." -#: ../../mod/newmember.php:22 ../../mod/admin.php:1104 -#: ../../mod/admin.php:1325 ../../mod/settings.php:85 -#: ../../include/nav.php:172 ../../view/theme/diabook/theme.php:544 -#: ../../view/theme/diabook/theme.php:648 +#: mod/newmember.php:22 mod/admin.php:1418 mod/admin.php:1676 +#: mod/settings.php:109 include/nav.php:182 view/theme/diabook/theme.php:544 +#: view/theme/diabook/theme.php:648 msgid "Settings" msgstr "Настройки" -#: ../../mod/newmember.php:26 +#: mod/newmember.php:26 msgid "Go to Your Settings" msgstr "Перейти к вашим настройкам" -#: ../../mod/newmember.php:26 +#: mod/newmember.php:26 msgid "" "On your <em>Settings</em> page - change your initial password. Also make a " "note of your Identity Address. This looks just like an email address - and " "will be useful in making friends on the free social web." -msgstr "На вашей странице <em>Настройки</em> - вы можете изменить свой первоначальный пароль. Также обратите внимание на ваш личный адрес. Он выглядит так же, как адрес электронной почты - и будет полезен для поиска друзей в свободной социальной сети." +msgstr "" +"На вашей странице <em>Настройки</em> - вы можете изменить свой " +"первоначальный пароль. Также обратите внимание на ваш личный адрес. Он " +"выглядит так же, как адрес электронной почты - и будет полезен для поиска " +"друзей в свободной социальной сети." -#: ../../mod/newmember.php:28 +#: mod/newmember.php:28 msgid "" -"Review the other settings, particularly the privacy settings. An unpublished" -" directory listing is like having an unlisted phone number. In general, you " +"Review the other settings, particularly the privacy settings. An unpublished " +"directory listing is like having an unlisted phone number. In general, you " "should probably publish your listing - unless all of your friends and " "potential friends know exactly how to find you." -msgstr "Просмотрите другие установки, в частности, параметры конфиденциальности. Неопубликованные пункты каталога с частными номерами телефона. В общем, вам, вероятно, следует опубликовать свою информацию - если все ваши друзья и потенциальные друзья точно знают, как вас найти." +msgstr "" +"Просмотрите другие установки, в частности, параметры конфиденциальности. " +"Неопубликованные пункты каталога с частными номерами телефона. В общем, вам, " +"вероятно, следует опубликовать свою информацию - если все ваши друзья и " +"потенциальные друзья точно знают, как вас найти." -#: ../../mod/newmember.php:36 ../../mod/profile_photo.php:244 -#: ../../mod/profiles.php:699 +#: mod/newmember.php:36 mod/profile_photo.php:250 mod/profiles.php:709 msgid "Upload Profile Photo" msgstr "Загрузить фото профиля" -#: ../../mod/newmember.php:36 +#: mod/newmember.php:36 msgid "" "Upload a profile photo if you have not done so already. Studies have shown " -"that people with real photos of themselves are ten times more likely to make" -" friends than people who do not." -msgstr "Загрузите фотографию профиля, если вы еще не сделали это. Исследования показали, что люди с реальными фотографиями имеют в десять раз больше шансов подружиться, чем люди, которые этого не делают." +"that people with real photos of themselves are ten times more likely to make " +"friends than people who do not." +msgstr "" +"Загрузите фотографию профиля, если вы еще не сделали это. Исследования " +"показали, что люди с реальными фотографиями имеют в десять раз больше шансов " +"подружиться, чем люди, которые этого не делают." -#: ../../mod/newmember.php:38 +#: mod/newmember.php:38 msgid "Edit Your Profile" msgstr "Редактировать профиль" -#: ../../mod/newmember.php:38 +#: mod/newmember.php:38 msgid "" "Edit your <strong>default</strong> profile to your liking. Review the " -"settings for hiding your list of friends and hiding the profile from unknown" -" visitors." -msgstr "Отредактируйте профиль <strong>по умолчанию</strong> на свой вкус. Просмотрите установки для сокрытия вашего списка друзей и сокрытия профиля от неизвестных посетителей." +"settings for hiding your list of friends and hiding the profile from unknown " +"visitors." +msgstr "" +"Отредактируйте профиль <strong>по умолчанию</strong> на свой вкус. " +"Просмотрите установки для сокрытия вашего списка друзей и сокрытия профиля " +"от неизвестных посетителей." -#: ../../mod/newmember.php:40 +#: mod/newmember.php:40 msgid "Profile Keywords" msgstr "Ключевые слова профиля" -#: ../../mod/newmember.php:40 +#: mod/newmember.php:40 msgid "" "Set some public keywords for your default profile which describe your " "interests. We may be able to find other people with similar interests and " "suggest friendships." -msgstr "Установите некоторые публичные ключевые слова для вашего профиля по умолчанию, которые описывают ваши интересы. Мы можем быть в состоянии найти других людей со схожими интересами и предложить дружбу." +msgstr "" +"Установите некоторые публичные ключевые слова для вашего профиля по " +"умолчанию, которые описывают ваши интересы. Мы можем быть в состоянии найти " +"других людей со схожими интересами и предложить дружбу." -#: ../../mod/newmember.php:44 +#: mod/newmember.php:44 msgid "Connecting" msgstr "Подключение" -#: ../../mod/newmember.php:49 ../../mod/newmember.php:51 -#: ../../include/contact_selectors.php:81 -msgid "Facebook" -msgstr "Facebook" - -#: ../../mod/newmember.php:49 -msgid "" -"Authorise the Facebook Connector if you currently have a Facebook account " -"and we will (optionally) import all your Facebook friends and conversations." -msgstr "Авторизуйте Facebook Connector , если у вас уже есть аккаунт на Facebook, и мы (по желанию) импортируем всех ваших друзей и беседы с Facebook." - -#: ../../mod/newmember.php:51 -msgid "" -"<em>If</em> this is your own personal server, installing the Facebook addon " -"may ease your transition to the free social web." -msgstr "<em>Если</em> это ваш личный сервер, установите дополнение Facebook, это может облегчить ваш переход на свободную социальную сеть." - -#: ../../mod/newmember.php:56 +#: mod/newmember.php:51 msgid "Importing Emails" msgstr "Импортирование Email-ов" -#: ../../mod/newmember.php:56 +#: mod/newmember.php:51 msgid "" "Enter your email access information on your Connector Settings page if you " "wish to import and interact with friends or mailing lists from your email " "INBOX" -msgstr "Введите информацию о доступе к вашему email на странице настроек вашего коннектора, если вы хотите импортировать, и общаться с друзьями или получать рассылки на ваш ящик электронной почты" +msgstr "" +"Введите информацию о доступе к вашему email на странице настроек вашего " +"коннектора, если вы хотите импортировать, и общаться с друзьями или получать " +"рассылки на ваш ящик электронной почты" -#: ../../mod/newmember.php:58 +#: mod/newmember.php:53 msgid "Go to Your Contacts Page" msgstr "Перейти на страницу ваших контактов" -#: ../../mod/newmember.php:58 +#: mod/newmember.php:53 msgid "" "Your Contacts page is your gateway to managing friendships and connecting " "with friends on other networks. Typically you enter their address or site " "URL in the <em>Add New Contact</em> dialog." -msgstr "Ваша страница контактов - это ваш шлюз к управлению дружбой и общением с друзьями в других сетях. Обычно вы вводите свой адрес или адрес сайта в диалог <em>Добавить новый контакт</em>." +msgstr "" +"Ваша страница контактов - это ваш шлюз к управлению дружбой и общением с " +"друзьями в других сетях. Обычно вы вводите свой адрес или адрес сайта в " +"диалог <em>Добавить новый контакт</em>." -#: ../../mod/newmember.php:60 +#: mod/newmember.php:55 msgid "Go to Your Site's Directory" msgstr "Перейти в каталог вашего сайта" -#: ../../mod/newmember.php:60 +#: mod/newmember.php:55 msgid "" "The Directory page lets you find other people in this network or other " "federated sites. Look for a <em>Connect</em> or <em>Follow</em> link on " "their profile page. Provide your own Identity Address if requested." -msgstr "На странице каталога вы можете найти других людей в этой сети или на других похожих сайтах. Ищите ссылки <em>Подключить</em> или <em>Следовать</em> на страницах их профилей. Укажите свой собственный адрес идентификации, если требуется." +msgstr "" +"На странице каталога вы можете найти других людей в этой сети или на других " +"похожих сайтах. Ищите ссылки <em>Подключить</em> или <em>Следовать</em> на " +"страницах их профилей. Укажите свой собственный адрес идентификации, если " +"требуется." -#: ../../mod/newmember.php:62 +#: mod/newmember.php:57 msgid "Finding New People" msgstr "Поиск людей" -#: ../../mod/newmember.php:62 +#: mod/newmember.php:57 msgid "" "On the side panel of the Contacts page are several tools to find new " "friends. We can match people by interest, look up people by name or " -"interest, and provide suggestions based on network relationships. On a brand" -" new site, friend suggestions will usually begin to be populated within 24 " +"interest, and provide suggestions based on network relationships. On a brand " +"new site, friend suggestions will usually begin to be populated within 24 " "hours." -msgstr "На боковой панели страницы Контакты есть несколько инструментов, чтобы найти новых друзей. Мы можем искать по соответствию интересам, посмотреть людей по имени или интересам, и внести предложения на основе сетевых отношений. На новом сайте, предложения дружбы, как правило, начинают заполняться в течение 24 часов." +msgstr "" +"На боковой панели страницы Контакты есть несколько инструментов, чтобы найти " +"новых друзей. Мы можем искать по соответствию интересам, посмотреть людей " +"по имени или интересам, и внести предложения на основе сетевых отношений. На " +"новом сайте, предложения дружбы, как правило, начинают заполняться в течение " +"24 часов." -#: ../../mod/newmember.php:66 ../../include/group.php:270 +#: mod/newmember.php:61 include/group.php:283 msgid "Groups" msgstr "Группы" -#: ../../mod/newmember.php:70 +#: mod/newmember.php:65 msgid "Group Your Contacts" msgstr "Группа \"ваши контакты\"" -#: ../../mod/newmember.php:70 +#: mod/newmember.php:65 msgid "" "Once you have made some friends, organize them into private conversation " -"groups from the sidebar of your Contacts page and then you can interact with" -" each group privately on your Network page." -msgstr "После того, как вы найдете несколько друзей, организуйте их в группы частных бесед в боковой панели на странице Контакты, а затем вы можете взаимодействовать с каждой группой приватно или на вашей странице Сеть." +"groups from the sidebar of your Contacts page and then you can interact with " +"each group privately on your Network page." +msgstr "" +"После того, как вы найдете несколько друзей, организуйте их в группы частных " +"бесед в боковой панели на странице Контакты, а затем вы можете " +"взаимодействовать с каждой группой приватно или на вашей странице Сеть." -#: ../../mod/newmember.php:73 +#: mod/newmember.php:68 msgid "Why Aren't My Posts Public?" msgstr "Почему мои посты не публичные?" -#: ../../mod/newmember.php:73 +#: mod/newmember.php:68 msgid "" -"Friendica respects your privacy. By default, your posts will only show up to" -" people you've added as friends. For more information, see the help section " +"Friendica respects your privacy. By default, your posts will only show up to " +"people you've added as friends. For more information, see the help section " "from the link above." -msgstr "Friendica уважает вашу приватность. По умолчанию, ваши сообщения будут показываться только для людей, которых вы добавили в список друзей. Для получения дополнительной информации см. раздел справки по ссылке выше." +msgstr "" +"Friendica уважает вашу приватность. По умолчанию, ваши сообщения будут " +"показываться только для людей, которых вы добавили в список друзей. Для " +"получения дополнительной информации см. раздел справки по ссылке выше." -#: ../../mod/newmember.php:78 +#: mod/newmember.php:73 msgid "Getting Help" msgstr "Получить помощь" -#: ../../mod/newmember.php:82 +#: mod/newmember.php:77 msgid "Go to the Help Section" msgstr "Перейти в раздел справки" -#: ../../mod/newmember.php:82 +#: mod/newmember.php:77 msgid "" -"Our <strong>help</strong> pages may be consulted for detail on other program" -" features and resources." -msgstr "Наши страницы <strong>помощи</strong> могут проконсультировать о подробностях и возможностях программы и ресурса." +"Our <strong>help</strong> pages may be consulted for detail on other program " +"features and resources." +msgstr "" +"Наши страницы <strong>помощи</strong> могут проконсультировать о " +"подробностях и возможностях программы и ресурса." -#: ../../mod/openid.php:24 +#: mod/openid.php:24 msgid "OpenID protocol error. No ID returned." msgstr "Ошибка протокола OpenID. Не возвращён ID." -#: ../../mod/openid.php:53 +#: mod/openid.php:53 msgid "" "Account not found and OpenID registration is not permitted on this site." msgstr "Аккаунт не найден и OpenID регистрация не допускается на этом сайте." -#: ../../mod/openid.php:93 ../../include/auth.php:112 -#: ../../include/auth.php:175 +#: mod/openid.php:93 include/auth.php:118 include/auth.php:181 msgid "Login failed." msgstr "Войти не удалось." -#: ../../mod/profile_photo.php:44 +#: mod/profile_photo.php:44 msgid "Image uploaded but image cropping failed." msgstr "Изображение загружено, но обрезка изображения не удалась." -#: ../../mod/profile_photo.php:74 ../../mod/profile_photo.php:81 -#: ../../mod/profile_photo.php:88 ../../mod/profile_photo.php:204 -#: ../../mod/profile_photo.php:296 ../../mod/profile_photo.php:305 -#: ../../mod/photos.php:155 ../../mod/photos.php:731 ../../mod/photos.php:1187 -#: ../../mod/photos.php:1210 ../../include/user.php:335 -#: ../../include/user.php:342 ../../include/user.php:349 -#: ../../view/theme/diabook/theme.php:500 +#: mod/profile_photo.php:74 mod/profile_photo.php:81 mod/profile_photo.php:88 +#: mod/profile_photo.php:210 mod/profile_photo.php:302 +#: mod/profile_photo.php:311 mod/photos.php:78 mod/photos.php:192 +#: mod/photos.php:775 mod/photos.php:1245 mod/photos.php:1268 +#: mod/photos.php:1862 include/user.php:345 include/user.php:352 +#: include/user.php:359 view/theme/diabook/theme.php:500 msgid "Profile Photos" msgstr "Фотографии профиля" -#: ../../mod/profile_photo.php:77 ../../mod/profile_photo.php:84 -#: ../../mod/profile_photo.php:91 ../../mod/profile_photo.php:308 +#: mod/profile_photo.php:77 mod/profile_photo.php:84 mod/profile_photo.php:91 +#: mod/profile_photo.php:314 #, php-format msgid "Image size reduction [%s] failed." msgstr "Уменьшение размера изображения [%s] не удалось." -#: ../../mod/profile_photo.php:118 +#: mod/profile_photo.php:124 msgid "" "Shift-reload the page or clear browser cache if the new photo does not " "display immediately." -msgstr "Перезагрузите страницу с зажатой клавишей \"Shift\" для того, чтобы увидеть свое новое фото немедленно." +msgstr "" +"Перезагрузите страницу с зажатой клавишей \"Shift\" для того, чтобы увидеть " +"свое новое фото немедленно." -#: ../../mod/profile_photo.php:128 +#: mod/profile_photo.php:134 msgid "Unable to process image" msgstr "Не удается обработать изображение" -#: ../../mod/profile_photo.php:144 ../../mod/wall_upload.php:122 +#: mod/profile_photo.php:150 mod/wall_upload.php:151 mod/photos.php:811 #, php-format -msgid "Image exceeds size limit of %d" -msgstr "Изображение превышает предельный размер %d" +msgid "Image exceeds size limit of %s" +msgstr "" -#: ../../mod/profile_photo.php:153 ../../mod/wall_upload.php:144 -#: ../../mod/photos.php:807 +#: mod/profile_photo.php:159 mod/wall_upload.php:183 mod/photos.php:851 msgid "Unable to process image." msgstr "Невозможно обработать фото." -#: ../../mod/profile_photo.php:242 +#: mod/profile_photo.php:248 msgid "Upload File:" msgstr "Загрузить файл:" -#: ../../mod/profile_photo.php:243 +#: mod/profile_photo.php:249 msgid "Select a profile:" msgstr "Выбрать этот профиль:" -#: ../../mod/profile_photo.php:245 +#: mod/profile_photo.php:251 msgid "Upload" msgstr "Загрузить" -#: ../../mod/profile_photo.php:248 ../../mod/settings.php:1062 +#: mod/profile_photo.php:254 msgid "or" msgstr "или" -#: ../../mod/profile_photo.php:248 +#: mod/profile_photo.php:254 msgid "skip this step" msgstr "пропустить этот шаг" -#: ../../mod/profile_photo.php:248 +#: mod/profile_photo.php:254 msgid "select a photo from your photo albums" msgstr "выберите фото из ваших фотоальбомов" -#: ../../mod/profile_photo.php:262 +#: mod/profile_photo.php:268 msgid "Crop Image" msgstr "Обрезать изображение" -#: ../../mod/profile_photo.php:263 +#: mod/profile_photo.php:269 msgid "Please adjust the image cropping for optimum viewing." msgstr "Пожалуйста, настройте обрезку изображения для оптимального просмотра." -#: ../../mod/profile_photo.php:265 +#: mod/profile_photo.php:271 msgid "Done Editing" msgstr "Редактирование выполнено" -#: ../../mod/profile_photo.php:299 +#: mod/profile_photo.php:305 msgid "Image uploaded successfully." msgstr "Изображение загружено успешно." -#: ../../mod/profile_photo.php:301 ../../mod/wall_upload.php:172 -#: ../../mod/photos.php:834 +#: mod/profile_photo.php:307 mod/wall_upload.php:216 mod/photos.php:878 msgid "Image upload failed." msgstr "Загрузка фото неудачная." -#: ../../mod/subthread.php:87 ../../mod/tagger.php:62 ../../mod/like.php:149 -#: ../../include/conversation.php:126 ../../include/conversation.php:254 -#: ../../include/text.php:1968 ../../include/diaspora.php:2087 -#: ../../view/theme/diabook/theme.php:471 +#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165 +#: include/conversation.php:130 include/conversation.php:266 +#: include/text.php:2000 include/diaspora.php:2169 +#: view/theme/diabook/theme.php:471 msgid "photo" msgstr "фото" -#: ../../mod/subthread.php:87 ../../mod/tagger.php:62 ../../mod/like.php:149 -#: ../../mod/like.php:319 ../../include/conversation.php:121 -#: ../../include/conversation.php:130 ../../include/conversation.php:249 -#: ../../include/conversation.php:258 ../../include/diaspora.php:2087 -#: ../../view/theme/diabook/theme.php:466 -#: ../../view/theme/diabook/theme.php:475 +#: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165 +#: include/like.php:334 include/conversation.php:125 +#: include/conversation.php:134 include/conversation.php:261 +#: include/conversation.php:270 include/diaspora.php:2169 +#: view/theme/diabook/theme.php:466 view/theme/diabook/theme.php:475 msgid "status" msgstr "статус" -#: ../../mod/subthread.php:103 +#: mod/subthread.php:103 #, php-format msgid "%1$s is following %2$s's %3$s" msgstr "" -#: ../../mod/tagrm.php:41 +#: mod/tagrm.php:41 msgid "Tag removed" msgstr "Ключевое слово удалено" -#: ../../mod/tagrm.php:79 +#: mod/tagrm.php:79 msgid "Remove Item Tag" msgstr "Удалить ключевое слово" -#: ../../mod/tagrm.php:81 +#: mod/tagrm.php:81 msgid "Select a tag to remove: " msgstr "Выберите ключевое слово для удаления: " -#: ../../mod/tagrm.php:93 ../../mod/delegate.php:139 +#: mod/tagrm.php:93 mod/delegate.php:139 msgid "Remove" msgstr "Удалить" -#: ../../mod/filer.php:30 ../../include/conversation.php:1006 -#: ../../include/conversation.php:1024 +#: mod/ostatus_subscribe.php:14 +msgid "Subscribing to OStatus contacts" +msgstr "" + +#: mod/ostatus_subscribe.php:25 +msgid "No contact provided." +msgstr "" + +#: mod/ostatus_subscribe.php:30 +msgid "Couldn't fetch information for contact." +msgstr "" + +#: mod/ostatus_subscribe.php:38 +msgid "Couldn't fetch friends for contact." +msgstr "" + +#: mod/ostatus_subscribe.php:51 mod/repair_ostatus.php:44 +msgid "Done" +msgstr "Готово" + +#: mod/ostatus_subscribe.php:65 +msgid "success" +msgstr "удачно" + +#: mod/ostatus_subscribe.php:67 +msgid "failed" +msgstr "неудача" + +#: mod/ostatus_subscribe.php:69 object/Item.php:235 +msgid "ignored" +msgstr "" + +#: mod/ostatus_subscribe.php:73 mod/repair_ostatus.php:50 +msgid "Keep this window open until done." +msgstr "" + +#: mod/filer.php:30 include/conversation.php:1132 +#: include/conversation.php:1150 msgid "Save to Folder:" msgstr "Сохранить в папку:" -#: ../../mod/filer.php:30 +#: mod/filer.php:30 msgid "- select -" msgstr "- выбрать -" -#: ../../mod/filer.php:31 ../../mod/editpost.php:109 ../../mod/notes.php:63 -#: ../../include/text.php:956 +#: mod/filer.php:31 mod/editpost.php:109 mod/notes.php:61 +#: include/text.php:1004 msgid "Save" msgstr "Сохранить" -#: ../../mod/follow.php:27 +#: mod/follow.php:19 mod/dfrn_request.php:870 +msgid "Submit Request" +msgstr "Отправить запрос" + +#: mod/follow.php:30 +msgid "You already added this contact." +msgstr "" + +#: mod/follow.php:39 +msgid "Diaspora support isn't enabled. Contact can't be added." +msgstr "" + +#: mod/follow.php:46 +msgid "OStatus support is disabled. Contact can't be added." +msgstr "" + +#: mod/follow.php:53 +msgid "The network type couldn't be detected. Contact can't be added." +msgstr "" + +#: mod/follow.php:109 mod/dfrn_request.php:856 +msgid "Please answer the following:" +msgstr "Пожалуйста, ответьте следующее:" + +#: mod/follow.php:110 mod/dfrn_request.php:857 +#, php-format +msgid "Does %s know you?" +msgstr "%s знает вас?" + +#: mod/follow.php:110 mod/settings.php:1103 mod/settings.php:1109 +#: mod/settings.php:1117 mod/settings.php:1121 mod/settings.php:1126 +#: mod/settings.php:1132 mod/settings.php:1138 mod/settings.php:1144 +#: mod/settings.php:1170 mod/settings.php:1171 mod/settings.php:1172 +#: mod/settings.php:1173 mod/settings.php:1174 mod/dfrn_request.php:857 +#: mod/register.php:239 mod/profiles.php:658 mod/profiles.php:662 +#: mod/profiles.php:687 mod/api.php:106 +msgid "No" +msgstr "Нет" + +#: mod/follow.php:111 mod/dfrn_request.php:861 +msgid "Add a personal note:" +msgstr "Добавить личную заметку:" + +#: mod/follow.php:117 mod/dfrn_request.php:867 +msgid "Your Identity Address:" +msgstr "Ваш идентификационный адрес:" + +#: mod/follow.php:180 msgid "Contact added" msgstr "Контакт добавлен" -#: ../../mod/item.php:113 +#: mod/item.php:114 msgid "Unable to locate original post." msgstr "Не удалось найти оригинальный пост." -#: ../../mod/item.php:345 +#: mod/item.php:329 msgid "Empty post discarded." msgstr "Пустое сообщение отбрасывается." -#: ../../mod/item.php:484 ../../mod/wall_upload.php:169 -#: ../../mod/wall_upload.php:178 ../../mod/wall_upload.php:185 -#: ../../include/Photo.php:916 ../../include/Photo.php:931 -#: ../../include/Photo.php:938 ../../include/Photo.php:960 -#: ../../include/message.php:144 +#: mod/item.php:467 mod/wall_upload.php:213 mod/wall_upload.php:227 +#: mod/wall_upload.php:234 include/Photo.php:958 include/Photo.php:973 +#: include/Photo.php:980 include/Photo.php:1002 include/message.php:145 msgid "Wall Photos" msgstr "Фото стены" -#: ../../mod/item.php:938 +#: mod/item.php:842 msgid "System error. Post not saved." msgstr "Системная ошибка. Сообщение не сохранено." -#: ../../mod/item.php:964 +#: mod/item.php:971 #, php-format msgid "" -"This message was sent to you by %s, a member of the Friendica social " -"network." -msgstr "Это сообщение было отправлено вам %s, участником социальной сети Friendica." +"This message was sent to you by %s, a member of the Friendica social network." +msgstr "" +"Это сообщение было отправлено вам %s, участником социальной сети Friendica." -#: ../../mod/item.php:966 +#: mod/item.php:973 #, php-format msgid "You may visit them online at %s" msgstr "Вы можете посетить их в онлайне на %s" -#: ../../mod/item.php:967 +#: mod/item.php:974 msgid "" "Please contact the sender by replying to this post if you do not wish to " "receive these messages." -msgstr "Пожалуйста, свяжитесь с отправителем, ответив на это сообщение, если вы не хотите получать эти сообщения." +msgstr "" +"Пожалуйста, свяжитесь с отправителем, ответив на это сообщение, если вы не " +"хотите получать эти сообщения." -#: ../../mod/item.php:971 +#: mod/item.php:978 #, php-format msgid "%s posted an update." msgstr "%s отправил/а/ обновление." -#: ../../mod/group.php:29 +#: mod/group.php:29 msgid "Group created." msgstr "Группа создана." -#: ../../mod/group.php:35 +#: mod/group.php:35 msgid "Could not create group." msgstr "Не удалось создать группу." -#: ../../mod/group.php:47 ../../mod/group.php:140 +#: mod/group.php:47 mod/group.php:140 msgid "Group not found." msgstr "Группа не найдена." -#: ../../mod/group.php:60 +#: mod/group.php:60 msgid "Group name changed." msgstr "Название группы изменено." -#: ../../mod/group.php:87 +#: mod/group.php:87 msgid "Save Group" msgstr "Сохранить группу" -#: ../../mod/group.php:93 +#: mod/group.php:93 msgid "Create a group of contacts/friends." msgstr "Создать группу контактов / друзей." -#: ../../mod/group.php:94 ../../mod/group.php:180 +#: mod/group.php:94 mod/group.php:178 include/group.php:289 msgid "Group Name: " msgstr "Название группы: " -#: ../../mod/group.php:113 +#: mod/group.php:113 msgid "Group removed." msgstr "Группа удалена." -#: ../../mod/group.php:115 +#: mod/group.php:115 msgid "Unable to remove group." msgstr "Не удается удалить группу." -#: ../../mod/group.php:179 +#: mod/group.php:177 msgid "Group Editor" msgstr "Редактор групп" -#: ../../mod/group.php:192 +#: mod/group.php:190 msgid "Members" msgstr "Участники" -#: ../../mod/apps.php:7 ../../index.php:212 +#: mod/group.php:193 mod/network.php:576 mod/content.php:130 +msgid "Group is empty" +msgstr "Группа пуста" + +#: mod/apps.php:7 index.php:226 msgid "You must be logged in to use addons. " msgstr "Вы должны войти в систему, чтобы использовать аддоны." -#: ../../mod/apps.php:11 +#: mod/apps.php:11 msgid "Applications" msgstr "Приложения" -#: ../../mod/apps.php:14 +#: mod/apps.php:14 msgid "No installed applications." msgstr "Нет установленных приложений." -#: ../../mod/dfrn_confirm.php:64 ../../mod/profiles.php:18 -#: ../../mod/profiles.php:133 ../../mod/profiles.php:179 -#: ../../mod/profiles.php:630 +#: mod/dfrn_confirm.php:64 mod/profiles.php:18 mod/profiles.php:133 +#: mod/profiles.php:179 mod/profiles.php:627 msgid "Profile not found." msgstr "Профиль не найден." -#: ../../mod/dfrn_confirm.php:120 ../../mod/fsuggest.php:20 -#: ../../mod/fsuggest.php:92 ../../mod/crepair.php:133 +#: mod/dfrn_confirm.php:120 mod/fsuggest.php:20 mod/fsuggest.php:92 +#: mod/crepair.php:131 msgid "Contact not found." msgstr "Контакт не найден." -#: ../../mod/dfrn_confirm.php:121 +#: mod/dfrn_confirm.php:121 msgid "" -"This may occasionally happen if contact was requested by both persons and it" -" has already been approved." -msgstr "Это может иногда происходить, если контакт запрашивали двое людей, и он был уже одобрен." +"This may occasionally happen if contact was requested by both persons and it " +"has already been approved." +msgstr "" +"Это может иногда происходить, если контакт запрашивали двое людей, и он был " +"уже одобрен." -#: ../../mod/dfrn_confirm.php:240 +#: mod/dfrn_confirm.php:240 msgid "Response from remote site was not understood." msgstr "Ответ от удаленного сайта не был понят." -#: ../../mod/dfrn_confirm.php:249 ../../mod/dfrn_confirm.php:254 +#: mod/dfrn_confirm.php:249 mod/dfrn_confirm.php:254 msgid "Unexpected response from remote site: " msgstr "Неожиданный ответ от удаленного сайта: " -#: ../../mod/dfrn_confirm.php:263 +#: mod/dfrn_confirm.php:263 msgid "Confirmation completed successfully." msgstr "Подтверждение успешно завершено." -#: ../../mod/dfrn_confirm.php:265 ../../mod/dfrn_confirm.php:279 -#: ../../mod/dfrn_confirm.php:286 +#: mod/dfrn_confirm.php:265 mod/dfrn_confirm.php:279 mod/dfrn_confirm.php:286 msgid "Remote site reported: " msgstr "Удаленный сайт сообщил: " -#: ../../mod/dfrn_confirm.php:277 +#: mod/dfrn_confirm.php:277 msgid "Temporary failure. Please wait and try again." msgstr "Временные неудачи. Подождите и попробуйте еще раз." -#: ../../mod/dfrn_confirm.php:284 +#: mod/dfrn_confirm.php:284 msgid "Introduction failed or was revoked." msgstr "Запрос ошибочен или был отозван." -#: ../../mod/dfrn_confirm.php:429 +#: mod/dfrn_confirm.php:430 msgid "Unable to set contact photo." msgstr "Не удается установить фото контакта." -#: ../../mod/dfrn_confirm.php:486 ../../include/conversation.php:172 -#: ../../include/diaspora.php:620 +#: mod/dfrn_confirm.php:487 include/conversation.php:185 +#: include/diaspora.php:637 #, php-format msgid "%1$s is now friends with %2$s" msgstr "%1$s и %2$s теперь друзья" -#: ../../mod/dfrn_confirm.php:571 +#: mod/dfrn_confirm.php:572 #, php-format msgid "No user record found for '%s' " msgstr "Не найдено записи пользователя для '%s' " -#: ../../mod/dfrn_confirm.php:581 +#: mod/dfrn_confirm.php:582 msgid "Our site encryption key is apparently messed up." msgstr "Наш ключ шифрования сайта, по-видимому, перепутался." -#: ../../mod/dfrn_confirm.php:592 +#: mod/dfrn_confirm.php:593 msgid "Empty site URL was provided or URL could not be decrypted by us." -msgstr "Был предоставлен пустой URL сайта или URL не может быть расшифрован нами." +msgstr "" +"Был предоставлен пустой URL сайта или URL не может быть расшифрован нами." -#: ../../mod/dfrn_confirm.php:613 +#: mod/dfrn_confirm.php:614 msgid "Contact record was not found for you on our site." msgstr "Запись контакта не найдена для вас на нашем сайте." -#: ../../mod/dfrn_confirm.php:627 +#: mod/dfrn_confirm.php:628 #, php-format msgid "Site public key not available in contact record for URL %s." msgstr "Публичный ключ недоступен в записи о контакте по ссылке %s" -#: ../../mod/dfrn_confirm.php:647 +#: mod/dfrn_confirm.php:648 msgid "" "The ID provided by your system is a duplicate on our system. It should work " "if you try again." -msgstr "ID, предложенный вашей системой, является дубликатом в нашей системе. Он должен работать, если вы повторите попытку." +msgstr "" +"ID, предложенный вашей системой, является дубликатом в нашей системе. Он " +"должен работать, если вы повторите попытку." -#: ../../mod/dfrn_confirm.php:658 +#: mod/dfrn_confirm.php:659 msgid "Unable to set your contact credentials on our system." msgstr "Не удалось установить ваши учетные данные контакта в нашей системе." -#: ../../mod/dfrn_confirm.php:725 +#: mod/dfrn_confirm.php:726 msgid "Unable to update your contact profile details on our system" msgstr "Не удается обновить ваши контактные детали профиля в нашей системе" -#: ../../mod/dfrn_confirm.php:752 ../../mod/dfrn_request.php:717 -#: ../../include/items.php:4008 +#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:741 include/items.php:4299 msgid "[Name Withheld]" msgstr "[Имя не разглашается]" -#: ../../mod/dfrn_confirm.php:797 +#: mod/dfrn_confirm.php:798 #, php-format msgid "%1$s has joined %2$s" msgstr "%1$s присоединился %2$s" -#: ../../mod/profile.php:21 ../../boot.php:1458 +#: mod/profile.php:21 include/identity.php:51 msgid "Requested profile is not available." msgstr "Запрашиваемый профиль недоступен." -#: ../../mod/profile.php:180 +#: mod/profile.php:179 msgid "Tips for New Members" msgstr "Советы для новых участников" -#: ../../mod/videos.php:125 +#: mod/videos.php:123 +msgid "Do you really want to delete this video?" +msgstr "" + +#: mod/videos.php:128 +msgid "Delete Video" +msgstr "Удалить видео" + +#: mod/videos.php:207 msgid "No videos selected" msgstr "Видео не выбрано" -#: ../../mod/videos.php:226 ../../mod/photos.php:1031 +#: mod/videos.php:308 mod/photos.php:1087 msgid "Access to this item is restricted." msgstr "Доступ к этому пункту ограничен." -#: ../../mod/videos.php:301 ../../include/text.php:1405 +#: mod/videos.php:383 include/text.php:1472 msgid "View Video" msgstr "Просмотреть видео" -#: ../../mod/videos.php:308 ../../mod/photos.php:1808 +#: mod/videos.php:390 mod/photos.php:1890 msgid "View Album" msgstr "Просмотреть альбом" -#: ../../mod/videos.php:317 +#: mod/videos.php:399 msgid "Recent Videos" msgstr "Последние видео" -#: ../../mod/videos.php:319 +#: mod/videos.php:401 msgid "Upload New Videos" msgstr "Загрузить новые видео" -#: ../../mod/tagger.php:95 ../../include/conversation.php:266 +#: mod/tagger.php:95 include/conversation.php:278 #, php-format msgid "%1$s tagged %2$s's %3$s with %4$s" msgstr "%1$s tagged %2$s's %3$s в %4$s" -#: ../../mod/fsuggest.php:63 +#: mod/fsuggest.php:63 msgid "Friend suggestion sent." msgstr "Приглашение в друзья отправлено." -#: ../../mod/fsuggest.php:97 +#: mod/fsuggest.php:97 msgid "Suggest Friends" msgstr "Предложить друзей" -#: ../../mod/fsuggest.php:99 +#: mod/fsuggest.php:99 #, php-format msgid "Suggest a friend for %s" msgstr "Предложить друга для %s." -#: ../../mod/lostpass.php:19 +#: mod/wall_upload.php:20 mod/wall_upload.php:33 mod/wall_upload.php:86 +#: mod/wall_upload.php:122 mod/wall_upload.php:125 mod/wall_attach.php:17 +#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1781 +msgid "Invalid request." +msgstr "Неверный запрос." + +#: mod/lostpass.php:19 msgid "No valid account found." msgstr "Не найдено действительного аккаунта." -#: ../../mod/lostpass.php:35 +#: mod/lostpass.php:35 msgid "Password reset request issued. Check your email." msgstr "Запрос на сброс пароля принят. Проверьте вашу электронную почту." -#: ../../mod/lostpass.php:42 +#: mod/lostpass.php:42 #, php-format msgid "" "\n" "\t\tDear %1$s,\n" "\t\t\tA request was recently received at \"%2$s\" to reset your account\n" -"\t\tpassword. In order to confirm this request, please select the verification link\n" +"\t\tpassword. In order to confirm this request, please select the " +"verification link\n" "\t\tbelow or paste it into your web browser address bar.\n" "\n" "\t\tIf you did NOT request this change, please DO NOT follow the link\n" @@ -1169,7 +1336,7 @@ msgid "" "\t\tissued this request." msgstr "" -#: ../../mod/lostpass.php:53 +#: mod/lostpass.php:53 #, php-format msgid "" "\n" @@ -1178,7 +1345,8 @@ msgid "" "\t\t%1$s\n" "\n" "\t\tYou will then receive a follow-up message containing the new password.\n" -"\t\tYou may change that password from your account settings page after logging in.\n" +"\t\tYou may change that password from your account settings page after " +"logging in.\n" "\n" "\t\tThe login details are as follows:\n" "\n" @@ -1186,55 +1354,60 @@ msgid "" "\t\tLogin Name:\t%3$s" msgstr "" -#: ../../mod/lostpass.php:72 +#: mod/lostpass.php:72 #, php-format msgid "Password reset requested at %s" msgstr "Запрос на сброс пароля получен %s" -#: ../../mod/lostpass.php:92 +#: mod/lostpass.php:92 msgid "" "Request could not be verified. (You may have previously submitted it.) " "Password reset failed." -msgstr "Запрос не может быть проверен. (Вы, возможно, ранее представляли его.) Попытка сброса пароля неудачная." +msgstr "" +"Запрос не может быть проверен. (Вы, возможно, ранее представляли его.) " +"Попытка сброса пароля неудачная." -#: ../../mod/lostpass.php:109 ../../boot.php:1280 +#: mod/lostpass.php:109 boot.php:1444 msgid "Password Reset" msgstr "Сброс пароля" -#: ../../mod/lostpass.php:110 +#: mod/lostpass.php:110 msgid "Your password has been reset as requested." msgstr "Ваш пароль был сброшен по требованию." -#: ../../mod/lostpass.php:111 +#: mod/lostpass.php:111 msgid "Your new password is" msgstr "Ваш новый пароль" -#: ../../mod/lostpass.php:112 +#: mod/lostpass.php:112 msgid "Save or copy your new password - and then" msgstr "Сохраните или скопируйте новый пароль - и затем" -#: ../../mod/lostpass.php:113 +#: mod/lostpass.php:113 msgid "click here to login" msgstr "нажмите здесь для входа" -#: ../../mod/lostpass.php:114 +#: mod/lostpass.php:114 msgid "" "Your password may be changed from the <em>Settings</em> page after " "successful login." -msgstr "Ваш пароль может быть изменен на странице <em>Настройки</em> после успешного входа." +msgstr "" +"Ваш пароль может быть изменен на странице <em>Настройки</em> после успешного " +"входа." -#: ../../mod/lostpass.php:125 +#: mod/lostpass.php:125 #, php-format msgid "" "\n" "\t\t\t\tDear %1$s,\n" "\t\t\t\t\tYour password has been changed as requested. Please retain this\n" -"\t\t\t\tinformation for your records (or change your password immediately to\n" +"\t\t\t\tinformation for your records (or change your password immediately " +"to\n" "\t\t\t\tsomething that you will remember).\n" "\t\t\t" msgstr "" -#: ../../mod/lostpass.php:131 +#: mod/lostpass.php:131 #, php-format msgid "" "\n" @@ -1244,1379 +1417,1693 @@ msgid "" "\t\t\t\tLogin Name:\t%2$s\n" "\t\t\t\tPassword:\t%3$s\n" "\n" -"\t\t\t\tYou may change that password from your account settings page after logging in.\n" +"\t\t\t\tYou may change that password from your account settings page after " +"logging in.\n" "\t\t\t" msgstr "" -#: ../../mod/lostpass.php:147 +#: mod/lostpass.php:147 #, php-format msgid "Your password has been changed at %s" msgstr "Ваш пароль был изменен %s" -#: ../../mod/lostpass.php:159 +#: mod/lostpass.php:159 msgid "Forgot your Password?" msgstr "Забыли пароль?" -#: ../../mod/lostpass.php:160 +#: mod/lostpass.php:160 msgid "" "Enter your email address and submit to have your password reset. Then check " "your email for further instructions." -msgstr "Введите адрес электронной почты и подтвердите, что вы хотите сбросить ваш пароль. Затем проверьте свою электронную почту для получения дальнейших инструкций." +msgstr "" +"Введите адрес электронной почты и подтвердите, что вы хотите сбросить ваш " +"пароль. Затем проверьте свою электронную почту для получения дальнейших " +"инструкций." -#: ../../mod/lostpass.php:161 +#: mod/lostpass.php:161 msgid "Nickname or Email: " msgstr "Ник или E-mail: " -#: ../../mod/lostpass.php:162 +#: mod/lostpass.php:162 msgid "Reset" msgstr "Сброс" -#: ../../mod/like.php:166 ../../include/conversation.php:137 -#: ../../include/diaspora.php:2103 ../../view/theme/diabook/theme.php:480 -#, php-format -msgid "%1$s likes %2$s's %3$s" -msgstr "%1$s нравится %3$s от %2$s " - -#: ../../mod/like.php:168 ../../include/conversation.php:140 -#, php-format -msgid "%1$s doesn't like %2$s's %3$s" -msgstr "%1$s не нравится %3$s от %2$s " - -#: ../../mod/ping.php:240 +#: mod/ping.php:265 msgid "{0} wants to be your friend" msgstr "{0} хочет стать Вашим другом" -#: ../../mod/ping.php:245 +#: mod/ping.php:280 msgid "{0} sent you a message" msgstr "{0} отправил Вам сообщение" -#: ../../mod/ping.php:250 +#: mod/ping.php:295 msgid "{0} requested registration" msgstr "{0} требуемая регистрация" -#: ../../mod/ping.php:256 -#, php-format -msgid "{0} commented %s's post" -msgstr "{0} прокомментировал сообщение от %s" - -#: ../../mod/ping.php:261 -#, php-format -msgid "{0} liked %s's post" -msgstr "{0} нравится сообщение от %s" - -#: ../../mod/ping.php:266 -#, php-format -msgid "{0} disliked %s's post" -msgstr "{0} не нравится сообщение от %s" - -#: ../../mod/ping.php:271 -#, php-format -msgid "{0} is now friends with %s" -msgstr "{0} теперь друзья с %s" - -#: ../../mod/ping.php:276 -msgid "{0} posted" -msgstr "{0} опубликовано" - -#: ../../mod/ping.php:281 -#, php-format -msgid "{0} tagged %s's post with #%s" -msgstr "{0} пометил сообщение %s с #%s" - -#: ../../mod/ping.php:287 -msgid "{0} mentioned you in a post" -msgstr "{0} упоменул Вас в сообщение" - -#: ../../mod/viewcontacts.php:41 +#: mod/viewcontacts.php:72 msgid "No contacts." msgstr "Нет контактов." -#: ../../mod/viewcontacts.php:78 ../../include/text.php:876 -msgid "View Contacts" -msgstr "Просмотр контактов" - -#: ../../mod/notifications.php:26 +#: mod/notifications.php:29 msgid "Invalid request identifier." msgstr "Неверный идентификатор запроса." -#: ../../mod/notifications.php:35 ../../mod/notifications.php:165 -#: ../../mod/notifications.php:211 +#: mod/notifications.php:38 mod/notifications.php:180 +#: mod/notifications.php:260 msgid "Discard" msgstr "Отказаться" -#: ../../mod/notifications.php:78 +#: mod/notifications.php:81 msgid "System" msgstr "Система" -#: ../../mod/notifications.php:83 ../../include/nav.php:145 +#: mod/notifications.php:87 mod/admin.php:390 include/nav.php:154 msgid "Network" -msgstr "Сеть" +msgstr "Новости" -#: ../../mod/notifications.php:88 ../../mod/network.php:371 +#: mod/notifications.php:93 mod/network.php:384 msgid "Personal" msgstr "Персонал" -#: ../../mod/notifications.php:93 ../../include/nav.php:105 -#: ../../include/nav.php:148 ../../view/theme/diabook/theme.php:123 +#: mod/notifications.php:99 include/nav.php:104 include/nav.php:157 +#: view/theme/diabook/theme.php:123 msgid "Home" -msgstr "Главная" +msgstr "Мой профиль" -#: ../../mod/notifications.php:98 ../../include/nav.php:154 +#: mod/notifications.php:105 include/nav.php:162 msgid "Introductions" msgstr "Запросы" -#: ../../mod/notifications.php:122 +#: mod/notifications.php:130 msgid "Show Ignored Requests" msgstr "Показать проигнорированные запросы" -#: ../../mod/notifications.php:122 +#: mod/notifications.php:130 msgid "Hide Ignored Requests" msgstr "Скрыть проигнорированные запросы" -#: ../../mod/notifications.php:149 ../../mod/notifications.php:195 +#: mod/notifications.php:164 mod/notifications.php:234 msgid "Notification type: " msgstr "Тип уведомления: " -#: ../../mod/notifications.php:150 +#: mod/notifications.php:165 msgid "Friend Suggestion" msgstr "Предложение в друзья" -#: ../../mod/notifications.php:152 +#: mod/notifications.php:167 #, php-format msgid "suggested by %s" msgstr "предложено юзером %s" -#: ../../mod/notifications.php:158 ../../mod/notifications.php:205 +#: mod/notifications.php:173 mod/notifications.php:252 msgid "Post a new friend activity" msgstr "Настроение" -#: ../../mod/notifications.php:158 ../../mod/notifications.php:205 +#: mod/notifications.php:173 mod/notifications.php:252 msgid "if applicable" msgstr "если требуется" -#: ../../mod/notifications.php:161 ../../mod/notifications.php:208 -#: ../../mod/admin.php:1005 +#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1308 msgid "Approve" msgstr "Одобрить" -#: ../../mod/notifications.php:181 +#: mod/notifications.php:196 msgid "Claims to be known to you: " msgstr "Утверждения, о которых должно быть вам известно: " -#: ../../mod/notifications.php:181 +#: mod/notifications.php:196 msgid "yes" msgstr "да" -#: ../../mod/notifications.php:181 +#: mod/notifications.php:196 msgid "no" msgstr "нет" -#: ../../mod/notifications.php:188 -msgid "Approve as: " -msgstr "Утвердить как: " +#: mod/notifications.php:197 +msgid "" +"Shall your connection be bidirectional or not? \"Friend\" implies that you " +"allow to read and you subscribe to their posts. \"Fan/Admirer\" means that " +"you allow to read but you do not want to read theirs. Approve as: " +msgstr "" -#: ../../mod/notifications.php:189 +#: mod/notifications.php:200 +msgid "" +"Shall your connection be bidirectional or not? \"Friend\" implies that you " +"allow to read and you subscribe to their posts. \"Sharer\" means that you " +"allow to read but you do not want to read theirs. Approve as: " +msgstr "" + +#: mod/notifications.php:208 msgid "Friend" msgstr "Друг" -#: ../../mod/notifications.php:190 +#: mod/notifications.php:209 msgid "Sharer" msgstr "Участник" -#: ../../mod/notifications.php:190 +#: mod/notifications.php:209 msgid "Fan/Admirer" msgstr "Фанат / Поклонник" -#: ../../mod/notifications.php:196 +#: mod/notifications.php:235 msgid "Friend/Connect Request" msgstr "Запрос в друзья / на подключение" -#: ../../mod/notifications.php:196 +#: mod/notifications.php:235 msgid "New Follower" msgstr "Новый фолловер" -#: ../../mod/notifications.php:217 +#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:310 +#: include/identity.php:590 +msgid "Gender:" +msgstr "Пол:" + +#: mod/notifications.php:266 msgid "No introductions." msgstr "Запросов нет." -#: ../../mod/notifications.php:220 ../../include/nav.php:155 +#: mod/notifications.php:269 include/nav.php:165 msgid "Notifications" msgstr "Уведомления" -#: ../../mod/notifications.php:258 ../../mod/notifications.php:387 -#: ../../mod/notifications.php:478 +#: mod/notifications.php:307 mod/notifications.php:436 +#: mod/notifications.php:527 #, php-format msgid "%s liked %s's post" msgstr "%s нравится %s сообшение" -#: ../../mod/notifications.php:268 ../../mod/notifications.php:397 -#: ../../mod/notifications.php:488 +#: mod/notifications.php:317 mod/notifications.php:446 +#: mod/notifications.php:537 #, php-format msgid "%s disliked %s's post" msgstr "%s не нравится %s сообшение" -#: ../../mod/notifications.php:283 ../../mod/notifications.php:412 -#: ../../mod/notifications.php:503 +#: mod/notifications.php:332 mod/notifications.php:461 +#: mod/notifications.php:552 #, php-format msgid "%s is now friends with %s" msgstr "%s теперь друзья с %s" -#: ../../mod/notifications.php:290 ../../mod/notifications.php:419 +#: mod/notifications.php:339 mod/notifications.php:468 #, php-format msgid "%s created a new post" msgstr "%s написал новое сообщение" -#: ../../mod/notifications.php:291 ../../mod/notifications.php:420 -#: ../../mod/notifications.php:513 +#: mod/notifications.php:340 mod/notifications.php:469 +#: mod/notifications.php:562 #, php-format msgid "%s commented on %s's post" msgstr "%s прокомментировал %s сообщение" -#: ../../mod/notifications.php:306 +#: mod/notifications.php:355 msgid "No more network notifications." msgstr "Уведомлений из сети больше нет." -#: ../../mod/notifications.php:310 +#: mod/notifications.php:359 msgid "Network Notifications" msgstr "Уведомления сети" -#: ../../mod/notifications.php:336 ../../mod/notify.php:75 +#: mod/notifications.php:385 mod/notify.php:72 msgid "No more system notifications." msgstr "Системных уведомлений больше нет." -#: ../../mod/notifications.php:340 ../../mod/notify.php:79 +#: mod/notifications.php:389 mod/notify.php:76 msgid "System Notifications" msgstr "Уведомления системы" -#: ../../mod/notifications.php:435 +#: mod/notifications.php:484 msgid "No more personal notifications." msgstr "Персональных уведомлений больше нет." -#: ../../mod/notifications.php:439 +#: mod/notifications.php:488 msgid "Personal Notifications" msgstr "Личные уведомления" -#: ../../mod/notifications.php:520 +#: mod/notifications.php:569 msgid "No more home notifications." msgstr "Уведомлений больше нет." -#: ../../mod/notifications.php:524 +#: mod/notifications.php:573 msgid "Home Notifications" msgstr "Уведомления" -#: ../../mod/babel.php:17 +#: mod/babel.php:17 msgid "Source (bbcode) text:" msgstr "Код (bbcode):" -#: ../../mod/babel.php:23 +#: mod/babel.php:23 msgid "Source (Diaspora) text to convert to BBcode:" msgstr "Код (Diaspora) для конвертации в BBcode:" -#: ../../mod/babel.php:31 +#: mod/babel.php:31 msgid "Source input: " msgstr "Ввести код:" -#: ../../mod/babel.php:35 +#: mod/babel.php:35 msgid "bb2html (raw HTML): " msgstr "bb2html (raw HTML): " -#: ../../mod/babel.php:39 +#: mod/babel.php:39 msgid "bb2html: " msgstr "bb2html: " -#: ../../mod/babel.php:43 +#: mod/babel.php:43 msgid "bb2html2bb: " msgstr "bb2html2bb: " -#: ../../mod/babel.php:47 +#: mod/babel.php:47 msgid "bb2md: " msgstr "bb2md: " -#: ../../mod/babel.php:51 +#: mod/babel.php:51 msgid "bb2md2html: " msgstr "bb2md2html: " -#: ../../mod/babel.php:55 +#: mod/babel.php:55 msgid "bb2dia2bb: " msgstr "bb2dia2bb: " -#: ../../mod/babel.php:59 +#: mod/babel.php:59 msgid "bb2md2html2bb: " msgstr "bb2md2html2bb: " -#: ../../mod/babel.php:69 +#: mod/babel.php:69 msgid "Source input (Diaspora format): " msgstr "Ввод кода (формат Diaspora):" -#: ../../mod/babel.php:74 +#: mod/babel.php:74 msgid "diaspora2bb: " msgstr "diaspora2bb: " -#: ../../mod/navigation.php:20 ../../include/nav.php:34 +#: mod/navigation.php:19 include/nav.php:33 msgid "Nothing new here" msgstr "Ничего нового здесь" -#: ../../mod/navigation.php:24 ../../include/nav.php:38 +#: mod/navigation.php:23 include/nav.php:37 msgid "Clear notifications" msgstr "Стереть уведомления" -#: ../../mod/message.php:9 ../../include/nav.php:164 +#: mod/message.php:15 include/nav.php:174 msgid "New Message" msgstr "Новое сообщение" -#: ../../mod/message.php:63 ../../mod/wallmessage.php:56 +#: mod/message.php:70 mod/wallmessage.php:56 msgid "No recipient selected." msgstr "Не выбран получатель." -#: ../../mod/message.php:67 +#: mod/message.php:74 msgid "Unable to locate contact information." msgstr "Не удалось найти контактную информацию." -#: ../../mod/message.php:70 ../../mod/wallmessage.php:62 +#: mod/message.php:77 mod/wallmessage.php:62 msgid "Message could not be sent." msgstr "Сообщение не может быть отправлено." -#: ../../mod/message.php:73 ../../mod/wallmessage.php:65 +#: mod/message.php:80 mod/wallmessage.php:65 msgid "Message collection failure." msgstr "Неудача коллекции сообщения." -#: ../../mod/message.php:76 ../../mod/wallmessage.php:68 +#: mod/message.php:83 mod/wallmessage.php:68 msgid "Message sent." msgstr "Сообщение отправлено." -#: ../../mod/message.php:182 ../../include/nav.php:161 +#: mod/message.php:189 include/nav.php:171 msgid "Messages" msgstr "Сообщения" -#: ../../mod/message.php:207 +#: mod/message.php:214 msgid "Do you really want to delete this message?" msgstr "Вы действительно хотите удалить это сообщение?" -#: ../../mod/message.php:227 +#: mod/message.php:234 msgid "Message deleted." msgstr "Сообщение удалено." -#: ../../mod/message.php:258 +#: mod/message.php:265 msgid "Conversation removed." msgstr "Беседа удалена." -#: ../../mod/message.php:283 ../../mod/message.php:291 -#: ../../mod/message.php:466 ../../mod/message.php:474 -#: ../../mod/wallmessage.php:127 ../../mod/wallmessage.php:135 -#: ../../include/conversation.php:1002 ../../include/conversation.php:1020 +#: mod/message.php:290 mod/message.php:298 mod/message.php:427 +#: mod/message.php:435 mod/wallmessage.php:127 mod/wallmessage.php:135 +#: include/conversation.php:1128 include/conversation.php:1146 msgid "Please enter a link URL:" msgstr "Пожалуйста, введите URL ссылки:" -#: ../../mod/message.php:319 ../../mod/wallmessage.php:142 +#: mod/message.php:326 mod/wallmessage.php:142 msgid "Send Private Message" msgstr "Отправить личное сообщение" -#: ../../mod/message.php:320 ../../mod/message.php:553 -#: ../../mod/wallmessage.php:144 +#: mod/message.php:327 mod/message.php:514 mod/wallmessage.php:144 msgid "To:" msgstr "Кому:" -#: ../../mod/message.php:325 ../../mod/message.php:555 -#: ../../mod/wallmessage.php:145 +#: mod/message.php:332 mod/message.php:516 mod/wallmessage.php:145 msgid "Subject:" msgstr "Тема:" -#: ../../mod/message.php:329 ../../mod/message.php:558 -#: ../../mod/wallmessage.php:151 ../../mod/invite.php:134 +#: mod/message.php:336 mod/message.php:519 mod/wallmessage.php:151 +#: mod/invite.php:134 msgid "Your message:" msgstr "Ваше сообщение:" -#: ../../mod/message.php:332 ../../mod/message.php:562 -#: ../../mod/wallmessage.php:154 ../../mod/editpost.php:110 -#: ../../include/conversation.php:1091 +#: mod/message.php:339 mod/message.php:523 mod/wallmessage.php:154 +#: mod/editpost.php:110 include/conversation.php:1183 msgid "Upload photo" msgstr "Загрузить фото" -#: ../../mod/message.php:333 ../../mod/message.php:563 -#: ../../mod/wallmessage.php:155 ../../mod/editpost.php:114 -#: ../../include/conversation.php:1095 +#: mod/message.php:340 mod/message.php:524 mod/wallmessage.php:155 +#: mod/editpost.php:114 include/conversation.php:1187 msgid "Insert web link" msgstr "Вставить веб-ссылку" -#: ../../mod/message.php:334 ../../mod/message.php:565 -#: ../../mod/content.php:499 ../../mod/content.php:883 -#: ../../mod/wallmessage.php:156 ../../mod/editpost.php:124 -#: ../../mod/photos.php:1545 ../../object/Item.php:364 -#: ../../include/conversation.php:692 ../../include/conversation.php:1109 +#: mod/message.php:341 mod/message.php:526 mod/content.php:501 +#: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:124 +#: mod/photos.php:1610 object/Item.php:396 include/conversation.php:713 +#: include/conversation.php:1201 msgid "Please wait" msgstr "Пожалуйста, подождите" -#: ../../mod/message.php:371 +#: mod/message.php:368 msgid "No messages." msgstr "Нет сообщений." -#: ../../mod/message.php:378 +#: mod/message.php:411 +msgid "Message not available." +msgstr "Сообщение не доступно." + +#: mod/message.php:481 +msgid "Delete message" +msgstr "Удалить сообщение" + +#: mod/message.php:507 mod/message.php:584 +msgid "Delete conversation" +msgstr "Удалить историю общения" + +#: mod/message.php:509 +msgid "" +"No secure communications available. You <strong>may</strong> be able to " +"respond from the sender's profile page." +msgstr "" +"Невозможно защищённое соединение. Вы <strong>имеете</strong> возможность " +"ответить со страницы профиля отправителя." + +#: mod/message.php:513 +msgid "Send Reply" +msgstr "Отправить ответ" + +#: mod/message.php:557 #, php-format msgid "Unknown sender - %s" msgstr "Неизвестный отправитель - %s" -#: ../../mod/message.php:381 +#: mod/message.php:560 #, php-format msgid "You and %s" msgstr "Вы и %s" -#: ../../mod/message.php:384 +#: mod/message.php:563 #, php-format msgid "%s and You" msgstr "%s и Вы" -#: ../../mod/message.php:405 ../../mod/message.php:546 -msgid "Delete conversation" -msgstr "Удалить историю общения" - -#: ../../mod/message.php:408 +#: mod/message.php:587 msgid "D, d M Y - g:i A" msgstr "D, d M Y - g:i A" -#: ../../mod/message.php:411 +#: mod/message.php:590 #, php-format msgid "%d message" msgid_plural "%d messages" msgstr[0] "%d сообщение" msgstr[1] "%d сообщений" msgstr[2] "%d сообщений" +msgstr[3] "%d сообщений" -#: ../../mod/message.php:450 -msgid "Message not available." -msgstr "Сообщение не доступно." - -#: ../../mod/message.php:520 -msgid "Delete message" -msgstr "Удалить сообщение" - -#: ../../mod/message.php:548 -msgid "" -"No secure communications available. You <strong>may</strong> be able to " -"respond from the sender's profile page." -msgstr "Невозможно защищённое соединение. Вы <strong>имеете</strong> возможность ответить со страницы профиля отправителя." - -#: ../../mod/message.php:552 -msgid "Send Reply" -msgstr "Отправить ответ" - -#: ../../mod/update_display.php:22 ../../mod/update_community.php:18 -#: ../../mod/update_notes.php:37 ../../mod/update_profile.php:41 -#: ../../mod/update_network.php:25 +#: mod/update_display.php:22 mod/update_community.php:18 +#: mod/update_notes.php:37 mod/update_profile.php:41 mod/update_network.php:25 msgid "[Embedded content - reload page to view]" msgstr "[Встроенное содержание - перезагрузите страницу для просмотра]" -#: ../../mod/crepair.php:106 +#: mod/crepair.php:104 msgid "Contact settings applied." msgstr "Установки контакта приняты." -#: ../../mod/crepair.php:108 +#: mod/crepair.php:106 msgid "Contact update failed." msgstr "Обновление контакта неудачное." -#: ../../mod/crepair.php:139 -msgid "Repair Contact Settings" -msgstr "Восстановить установки контакта" - -#: ../../mod/crepair.php:141 +#: mod/crepair.php:137 msgid "" -"<strong>WARNING: This is highly advanced</strong> and if you enter incorrect" -" information your communications with this contact may stop working." -msgstr "<strong>ВНИМАНИЕ: Это крайне важно!</strong> Если вы введете неверную информацию, ваша связь с этим контактом перестанет работать." +"<strong>WARNING: This is highly advanced</strong> and if you enter incorrect " +"information your communications with this contact may stop working." +msgstr "" +"<strong>ВНИМАНИЕ: Это крайне важно!</strong> Если вы введете неверную " +"информацию, ваша связь с этим контактом перестанет работать." -#: ../../mod/crepair.php:142 +#: mod/crepair.php:138 msgid "" "Please use your browser 'Back' button <strong>now</strong> if you are " "uncertain what to do on this page." -msgstr "Пожалуйста, нажмите клавишу вашего браузера 'Back' или 'Назад' <strong>сейчас</strong>, если вы не уверены, что делаете на этой странице." +msgstr "" +"Пожалуйста, нажмите клавишу вашего браузера 'Back' или 'Назад' " +"<strong>сейчас</strong>, если вы не уверены, что делаете на этой странице." -#: ../../mod/crepair.php:148 -msgid "Return to contact editor" -msgstr "Возврат к редактору контакта" - -#: ../../mod/crepair.php:159 ../../mod/crepair.php:161 +#: mod/crepair.php:151 mod/crepair.php:153 msgid "No mirroring" msgstr "" -#: ../../mod/crepair.php:159 +#: mod/crepair.php:151 msgid "Mirror as forwarded posting" msgstr "" -#: ../../mod/crepair.php:159 ../../mod/crepair.php:161 +#: mod/crepair.php:151 mod/crepair.php:153 msgid "Mirror as my own posting" msgstr "" -#: ../../mod/crepair.php:165 ../../mod/admin.php:1003 ../../mod/admin.php:1015 -#: ../../mod/admin.php:1016 ../../mod/admin.php:1029 -#: ../../mod/settings.php:616 ../../mod/settings.php:642 +#: mod/crepair.php:167 +msgid "Return to contact editor" +msgstr "Возврат к редактору контакта" + +#: mod/crepair.php:169 +msgid "Refetch contact data" +msgstr "" + +#: mod/crepair.php:170 mod/admin.php:1306 mod/admin.php:1318 +#: mod/admin.php:1319 mod/admin.php:1332 mod/settings.php:661 +#: mod/settings.php:687 msgid "Name" msgstr "Имя" -#: ../../mod/crepair.php:166 +#: mod/crepair.php:171 msgid "Account Nickname" msgstr "Ник аккаунта" -#: ../../mod/crepair.php:167 +#: mod/crepair.php:172 msgid "@Tagname - overrides Name/Nickname" msgstr "" -#: ../../mod/crepair.php:168 +#: mod/crepair.php:173 msgid "Account URL" msgstr "URL аккаунта" -#: ../../mod/crepair.php:169 +#: mod/crepair.php:174 msgid "Friend Request URL" msgstr "URL запроса в друзья" -#: ../../mod/crepair.php:170 +#: mod/crepair.php:175 msgid "Friend Confirm URL" msgstr "URL подтверждения друга" -#: ../../mod/crepair.php:171 +#: mod/crepair.php:176 msgid "Notification Endpoint URL" msgstr "URL эндпоинта уведомления" -#: ../../mod/crepair.php:172 +#: mod/crepair.php:177 msgid "Poll/Feed URL" msgstr "URL опроса/ленты" -#: ../../mod/crepair.php:173 +#: mod/crepair.php:178 msgid "New photo from this URL" msgstr "Новое фото из этой URL" -#: ../../mod/crepair.php:174 +#: mod/crepair.php:179 msgid "Remote Self" msgstr "" -#: ../../mod/crepair.php:176 +#: mod/crepair.php:182 msgid "Mirror postings from this contact" msgstr "" -#: ../../mod/crepair.php:176 +#: mod/crepair.php:184 msgid "" "Mark this contact as remote_self, this will cause friendica to repost new " "entries from this contact." msgstr "" -#: ../../mod/bookmarklet.php:12 ../../boot.php:1266 ../../include/nav.php:92 +#: mod/bookmarklet.php:12 boot.php:1430 include/nav.php:91 msgid "Login" msgstr "Вход" -#: ../../mod/bookmarklet.php:41 +#: mod/bookmarklet.php:41 msgid "The post was created" msgstr "" -#: ../../mod/viewsrc.php:7 +#: mod/viewsrc.php:7 msgid "Access denied." msgstr "Доступ запрещен." -#: ../../mod/dirfind.php:26 -msgid "People Search" -msgstr "Поиск людей" +#: mod/dirfind.php:194 mod/allfriends.php:80 mod/match.php:85 +#: mod/suggest.php:98 include/contact_widgets.php:10 include/identity.php:212 +msgid "Connect" +msgstr "Подключить" -#: ../../mod/dirfind.php:60 ../../mod/match.php:65 +#: mod/dirfind.php:195 mod/allfriends.php:64 mod/match.php:70 +#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:283 +#: include/Contact.php:296 include/Contact.php:338 +#: include/conversation.php:912 include/conversation.php:926 +msgid "View Profile" +msgstr "Просмотреть профиль" + +#: mod/dirfind.php:224 +#, php-format +msgid "People Search - %s" +msgstr "" + +#: mod/dirfind.php:231 mod/match.php:105 msgid "No matches" msgstr "Нет соответствий" -#: ../../mod/fbrowser.php:25 ../../boot.php:2126 ../../include/nav.php:78 -#: ../../view/theme/diabook/theme.php:126 +#: mod/fbrowser.php:32 include/identity.php:702 include/nav.php:77 +#: view/theme/diabook/theme.php:126 msgid "Photos" msgstr "Фото" -#: ../../mod/fbrowser.php:113 +#: mod/fbrowser.php:41 mod/fbrowser.php:62 mod/photos.php:62 +#: mod/photos.php:192 mod/photos.php:1119 mod/photos.php:1245 +#: mod/photos.php:1268 mod/photos.php:1838 mod/photos.php:1850 +#: view/theme/diabook/theme.php:499 +msgid "Contact Photos" +msgstr "Фотографии контакта" + +#: mod/fbrowser.php:125 msgid "Files" msgstr "Файлы" -#: ../../mod/nogroup.php:59 +#: mod/nogroup.php:63 msgid "Contacts who are not members of a group" msgstr "Контакты, которые не являются членами группы" -#: ../../mod/admin.php:57 +#: mod/admin.php:92 msgid "Theme settings updated." msgstr "Настройки темы обновлены." -#: ../../mod/admin.php:104 ../../mod/admin.php:619 +#: mod/admin.php:156 mod/admin.php:888 msgid "Site" msgstr "Сайт" -#: ../../mod/admin.php:105 ../../mod/admin.php:998 ../../mod/admin.php:1013 +#: mod/admin.php:157 mod/admin.php:832 mod/admin.php:1301 mod/admin.php:1316 msgid "Users" msgstr "Пользователи" -#: ../../mod/admin.php:106 ../../mod/admin.php:1102 ../../mod/admin.php:1155 -#: ../../mod/settings.php:57 +#: mod/admin.php:158 mod/admin.php:1416 mod/admin.php:1476 mod/settings.php:72 msgid "Plugins" msgstr "Плагины" -#: ../../mod/admin.php:107 ../../mod/admin.php:1323 ../../mod/admin.php:1357 +#: mod/admin.php:159 mod/admin.php:1674 mod/admin.php:1724 msgid "Themes" msgstr "Темы" -#: ../../mod/admin.php:108 +#: mod/admin.php:160 mod/settings.php:50 +msgid "Additional features" +msgstr "Дополнительные возможности" + +#: mod/admin.php:161 msgid "DB updates" msgstr "Обновление БД" -#: ../../mod/admin.php:123 ../../mod/admin.php:132 ../../mod/admin.php:1444 +#: mod/admin.php:162 mod/admin.php:385 +msgid "Inspect Queue" +msgstr "" + +#: mod/admin.php:163 mod/admin.php:354 +msgid "Federation Statistics" +msgstr "" + +#: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1792 msgid "Logs" msgstr "Журналы" -#: ../../mod/admin.php:124 +#: mod/admin.php:178 mod/admin.php:1859 +msgid "View Logs" +msgstr "Просмотр логов" + +#: mod/admin.php:179 msgid "probe address" msgstr "" -#: ../../mod/admin.php:125 +#: mod/admin.php:180 msgid "check webfinger" msgstr "" -#: ../../mod/admin.php:130 ../../include/nav.php:184 +#: mod/admin.php:186 include/nav.php:194 msgid "Admin" msgstr "Администратор" -#: ../../mod/admin.php:131 +#: mod/admin.php:187 msgid "Plugin Features" msgstr "Возможности плагина" -#: ../../mod/admin.php:133 +#: mod/admin.php:189 msgid "diagnostics" -msgstr "" +msgstr "Диагностика" -#: ../../mod/admin.php:134 +#: mod/admin.php:190 msgid "User registrations waiting for confirmation" msgstr "Регистрации пользователей, ожидающие подтверждения" -#: ../../mod/admin.php:193 ../../mod/admin.php:952 -msgid "Normal Account" -msgstr "Обычный аккаунт" +#: mod/admin.php:347 +msgid "" +"This page offers you some numbers to the known part of the federated social " +"network your Friendica node is part of. These numbers are not complete but " +"only reflect the part of the network your node is aware of." +msgstr "" -#: ../../mod/admin.php:194 ../../mod/admin.php:953 -msgid "Soapbox Account" -msgstr "Аккаунт Витрина" +#: mod/admin.php:348 +msgid "" +"The <em>Auto Discovered Contact Directory</em> feature is not enabled, it " +"will improve the data displayed here." +msgstr "" -#: ../../mod/admin.php:195 ../../mod/admin.php:954 -msgid "Community/Celebrity Account" -msgstr "Аккаунт Сообщество / Знаменитость" - -#: ../../mod/admin.php:196 ../../mod/admin.php:955 -msgid "Automatic Friend Account" -msgstr "\"Автоматический друг\" Аккаунт" - -#: ../../mod/admin.php:197 -msgid "Blog Account" -msgstr "Аккаунт блога" - -#: ../../mod/admin.php:198 -msgid "Private Forum" -msgstr "Личный форум" - -#: ../../mod/admin.php:217 -msgid "Message queues" -msgstr "Очереди сообщений" - -#: ../../mod/admin.php:222 ../../mod/admin.php:618 ../../mod/admin.php:997 -#: ../../mod/admin.php:1101 ../../mod/admin.php:1154 ../../mod/admin.php:1322 -#: ../../mod/admin.php:1356 ../../mod/admin.php:1443 +#: mod/admin.php:353 mod/admin.php:384 mod/admin.php:441 mod/admin.php:887 +#: mod/admin.php:1300 mod/admin.php:1415 mod/admin.php:1475 mod/admin.php:1673 +#: mod/admin.php:1723 mod/admin.php:1791 mod/admin.php:1858 msgid "Administration" msgstr "Администрация" -#: ../../mod/admin.php:223 +#: mod/admin.php:360 +#, php-format +msgid "Currently this node is aware of %d nodes from the following platforms:" +msgstr "" + +#: mod/admin.php:387 +msgid "ID" +msgstr "" + +#: mod/admin.php:388 +msgid "Recipient Name" +msgstr "" + +#: mod/admin.php:389 +msgid "Recipient Profile" +msgstr "" + +#: mod/admin.php:391 +msgid "Created" +msgstr "" + +#: mod/admin.php:392 +msgid "Last Tried" +msgstr "" + +#: mod/admin.php:393 +msgid "" +"This page lists the content of the queue for outgoing postings. These are " +"postings the initial delivery failed for. They will be resend later and " +"eventually deleted if the delivery fails permanently." +msgstr "" + +#: mod/admin.php:412 mod/admin.php:1254 +msgid "Normal Account" +msgstr "Обычный аккаунт" + +#: mod/admin.php:413 mod/admin.php:1255 +msgid "Soapbox Account" +msgstr "Аккаунт Витрина" + +#: mod/admin.php:414 mod/admin.php:1256 +msgid "Community/Celebrity Account" +msgstr "Аккаунт Сообщество / Знаменитость" + +#: mod/admin.php:415 mod/admin.php:1257 +msgid "Automatic Friend Account" +msgstr "\"Автоматический друг\" Аккаунт" + +#: mod/admin.php:416 +msgid "Blog Account" +msgstr "Аккаунт блога" + +#: mod/admin.php:417 +msgid "Private Forum" +msgstr "Личный форум" + +#: mod/admin.php:436 +msgid "Message queues" +msgstr "Очереди сообщений" + +#: mod/admin.php:442 msgid "Summary" msgstr "Резюме" -#: ../../mod/admin.php:225 +#: mod/admin.php:444 msgid "Registered users" msgstr "Зарегистрированные пользователи" -#: ../../mod/admin.php:227 +#: mod/admin.php:446 msgid "Pending registrations" msgstr "Ожидающие регистрации" -#: ../../mod/admin.php:228 +#: mod/admin.php:447 msgid "Version" msgstr "Версия" -#: ../../mod/admin.php:232 +#: mod/admin.php:452 msgid "Active plugins" msgstr "Активные плагины" -#: ../../mod/admin.php:255 +#: mod/admin.php:475 msgid "Can not parse base url. Must have at least <scheme>://<domain>" -msgstr "Невозможно определить базовый URL. Он должен иметь следующий вид - <scheme>://<domain>" +msgstr "" +"Невозможно определить базовый URL. Он должен иметь следующий вид - " +"<scheme>://<domain>" -#: ../../mod/admin.php:516 +#: mod/admin.php:760 +msgid "RINO2 needs mcrypt php extension to work." +msgstr "Для функционирования RINO2 необходим пакет php5-mcrypt" + +#: mod/admin.php:768 msgid "Site settings updated." msgstr "Установки сайта обновлены." -#: ../../mod/admin.php:545 ../../mod/settings.php:828 +#: mod/admin.php:796 mod/settings.php:912 msgid "No special theme for mobile devices" msgstr "Нет специальной темы для мобильных устройств" -#: ../../mod/admin.php:562 +#: mod/admin.php:815 msgid "No community page" msgstr "" -#: ../../mod/admin.php:563 +#: mod/admin.php:816 msgid "Public postings from users of this site" msgstr "" -#: ../../mod/admin.php:564 +#: mod/admin.php:817 msgid "Global community page" msgstr "" -#: ../../mod/admin.php:570 +#: mod/admin.php:823 msgid "At post arrival" msgstr "" -#: ../../mod/admin.php:571 ../../include/contact_selectors.php:56 +#: mod/admin.php:824 include/contact_selectors.php:56 msgid "Frequently" msgstr "Часто" -#: ../../mod/admin.php:572 ../../include/contact_selectors.php:57 +#: mod/admin.php:825 include/contact_selectors.php:57 msgid "Hourly" msgstr "Раз в час" -#: ../../mod/admin.php:573 ../../include/contact_selectors.php:58 +#: mod/admin.php:826 include/contact_selectors.php:58 msgid "Twice daily" msgstr "Два раза в день" -#: ../../mod/admin.php:574 ../../include/contact_selectors.php:59 +#: mod/admin.php:827 include/contact_selectors.php:59 msgid "Daily" msgstr "Ежедневно" -#: ../../mod/admin.php:579 +#: mod/admin.php:833 +msgid "Users, Global Contacts" +msgstr "" + +#: mod/admin.php:834 +msgid "Users, Global Contacts/fallback" +msgstr "" + +#: mod/admin.php:838 +msgid "One month" +msgstr "Один месяц" + +#: mod/admin.php:839 +msgid "Three months" +msgstr "Три месяца" + +#: mod/admin.php:840 +msgid "Half a year" +msgstr "Пол года" + +#: mod/admin.php:841 +msgid "One year" +msgstr "Один год" + +#: mod/admin.php:846 msgid "Multi user instance" msgstr "Многопользовательский вид" -#: ../../mod/admin.php:602 +#: mod/admin.php:869 msgid "Closed" msgstr "Закрыто" -#: ../../mod/admin.php:603 +#: mod/admin.php:870 msgid "Requires approval" msgstr "Требуется подтверждение" -#: ../../mod/admin.php:604 +#: mod/admin.php:871 msgid "Open" msgstr "Открыто" -#: ../../mod/admin.php:608 +#: mod/admin.php:875 msgid "No SSL policy, links will track page SSL state" msgstr "Нет режима SSL, состояние SSL не будет отслеживаться" -#: ../../mod/admin.php:609 +#: mod/admin.php:876 msgid "Force all links to use SSL" msgstr "Заставить все ссылки использовать SSL" -#: ../../mod/admin.php:610 +#: mod/admin.php:877 msgid "Self-signed certificate, use SSL for local links only (discouraged)" -msgstr "Само-подписанный сертификат, использовать SSL только локально (не рекомендуется)" +msgstr "" +"Само-подписанный сертификат, использовать SSL только локально (не " +"рекомендуется)" -#: ../../mod/admin.php:620 ../../mod/admin.php:1156 ../../mod/admin.php:1358 -#: ../../mod/admin.php:1445 ../../mod/settings.php:614 -#: ../../mod/settings.php:724 ../../mod/settings.php:798 -#: ../../mod/settings.php:880 ../../mod/settings.php:1113 +#: mod/admin.php:889 mod/admin.php:1477 mod/admin.php:1725 mod/admin.php:1793 +#: mod/admin.php:1942 mod/settings.php:659 mod/settings.php:769 +#: mod/settings.php:813 mod/settings.php:882 mod/settings.php:969 +#: mod/settings.php:1204 msgid "Save Settings" msgstr "Сохранить настройки" -#: ../../mod/admin.php:621 ../../mod/register.php:255 +#: mod/admin.php:890 mod/register.php:263 msgid "Registration" msgstr "Регистрация" -#: ../../mod/admin.php:622 +#: mod/admin.php:891 msgid "File upload" msgstr "Загрузка файлов" -#: ../../mod/admin.php:623 +#: mod/admin.php:892 msgid "Policies" msgstr "Политики" -#: ../../mod/admin.php:624 +#: mod/admin.php:893 msgid "Advanced" msgstr "Расширенный" -#: ../../mod/admin.php:625 +#: mod/admin.php:894 +msgid "Auto Discovered Contact Directory" +msgstr "" + +#: mod/admin.php:895 msgid "Performance" msgstr "Производительность" -#: ../../mod/admin.php:626 +#: mod/admin.php:896 msgid "" "Relocate - WARNING: advanced function. Could make this server unreachable." -msgstr "Переместить - ПРЕДУПРЕЖДЕНИЕ: расширеная функция. Может сделать этот сервер недоступным." +msgstr "" +"Переместить - ПРЕДУПРЕЖДЕНИЕ: расширеная функция. Может сделать этот сервер " +"недоступным." -#: ../../mod/admin.php:629 +#: mod/admin.php:899 msgid "Site name" msgstr "Название сайта" -#: ../../mod/admin.php:630 +#: mod/admin.php:900 msgid "Host name" -msgstr "" +msgstr "Имя хоста" -#: ../../mod/admin.php:631 +#: mod/admin.php:901 msgid "Sender Email" -msgstr "" +msgstr "Системный Email" -#: ../../mod/admin.php:632 +#: mod/admin.php:901 +msgid "" +"The email address your server shall use to send notification emails from." +msgstr "Адрес с которого будут приходить письма пользователям." + +#: mod/admin.php:902 msgid "Banner/Logo" msgstr "Баннер/Логотип" -#: ../../mod/admin.php:633 +#: mod/admin.php:903 msgid "Shortcut icon" msgstr "" -#: ../../mod/admin.php:634 +#: mod/admin.php:903 +msgid "Link to an icon that will be used for browsers." +msgstr "" + +#: mod/admin.php:904 msgid "Touch icon" msgstr "" -#: ../../mod/admin.php:635 +#: mod/admin.php:904 +msgid "Link to an icon that will be used for tablets and mobiles." +msgstr "" + +#: mod/admin.php:905 msgid "Additional Info" msgstr "Дополнительная информация" -#: ../../mod/admin.php:635 +#: mod/admin.php:905 +#, php-format msgid "" "For public servers: you can add additional information here that will be " -"listed at dir.friendica.com/siteinfo." -msgstr "Для публичных серверов: вы можете добавить дополнительную информацию, которая будет перечислена в dir.friendica.com/siteinfo." +"listed at %s/siteinfo." +msgstr "" -#: ../../mod/admin.php:636 +#: mod/admin.php:906 msgid "System language" msgstr "Системный язык" -#: ../../mod/admin.php:637 +#: mod/admin.php:907 msgid "System theme" msgstr "Системная тема" -#: ../../mod/admin.php:637 +#: mod/admin.php:907 msgid "" "Default system theme - may be over-ridden by user profiles - <a href='#' " "id='cnftheme'>change theme settings</a>" -msgstr "Тема системы по умолчанию - может быть переопределена пользователем - <a href='#' id='cnftheme'>изменить настройки темы</a>" +msgstr "" +"Тема системы по умолчанию - может быть переопределена пользователем - <a " +"href='#' id='cnftheme'>изменить настройки темы</a>" -#: ../../mod/admin.php:638 +#: mod/admin.php:908 msgid "Mobile system theme" msgstr "Мобильная тема системы" -#: ../../mod/admin.php:638 +#: mod/admin.php:908 msgid "Theme for mobile devices" msgstr "Тема для мобильных устройств" -#: ../../mod/admin.php:639 +#: mod/admin.php:909 msgid "SSL link policy" msgstr "Политика SSL" -#: ../../mod/admin.php:639 +#: mod/admin.php:909 msgid "Determines whether generated links should be forced to use SSL" msgstr "Ссылки должны быть вынуждены использовать SSL" -#: ../../mod/admin.php:640 +#: mod/admin.php:910 msgid "Force SSL" -msgstr "" +msgstr "SSL принудительно" -#: ../../mod/admin.php:640 +#: mod/admin.php:910 msgid "" -"Force all Non-SSL requests to SSL - Attention: on some systems it could lead" -" to endless loops." +"Force all Non-SSL requests to SSL - Attention: on some systems it could lead " +"to endless loops." msgstr "" -#: ../../mod/admin.php:641 +#: mod/admin.php:911 msgid "Old style 'Share'" msgstr "Старый стиль 'Share'" -#: ../../mod/admin.php:641 +#: mod/admin.php:911 msgid "Deactivates the bbcode element 'share' for repeating items." msgstr "Отключение BBCode элемента 'share' для повторяющихся элементов." -#: ../../mod/admin.php:642 +#: mod/admin.php:912 msgid "Hide help entry from navigation menu" msgstr "Скрыть пункт \"помощь\" в меню навигации" -#: ../../mod/admin.php:642 +#: mod/admin.php:912 msgid "" "Hides the menu entry for the Help pages from the navigation menu. You can " "still access it calling /help directly." -msgstr "Скрывает элемент меню для страницы справки из меню навигации. Вы все еще можете получить доступ к нему через вызов/помощь напрямую." +msgstr "" +"Скрывает элемент меню для страницы справки из меню навигации. Вы все еще " +"можете получить доступ к нему через вызов/помощь напрямую." -#: ../../mod/admin.php:643 +#: mod/admin.php:913 msgid "Single user instance" msgstr "Однопользовательский режим" -#: ../../mod/admin.php:643 +#: mod/admin.php:913 msgid "Make this instance multi-user or single-user for the named user" -msgstr "Сделать этот экземпляр многопользовательским, или однопользовательским для названного пользователя" +msgstr "" +"Сделать этот экземпляр многопользовательским, или однопользовательским для " +"названного пользователя" -#: ../../mod/admin.php:644 +#: mod/admin.php:914 msgid "Maximum image size" msgstr "Максимальный размер изображения" -#: ../../mod/admin.php:644 +#: mod/admin.php:914 msgid "" "Maximum size in bytes of uploaded images. Default is 0, which means no " "limits." -msgstr "Максимальный размер в байтах для загружаемых изображений. По умолчанию 0, что означает отсутствие ограничений." +msgstr "" +"Максимальный размер в байтах для загружаемых изображений. По умолчанию 0, " +"что означает отсутствие ограничений." -#: ../../mod/admin.php:645 +#: mod/admin.php:915 msgid "Maximum image length" msgstr "Максимальная длина картинки" -#: ../../mod/admin.php:645 +#: mod/admin.php:915 msgid "" "Maximum length in pixels of the longest side of uploaded images. Default is " "-1, which means no limits." -msgstr "Максимальная длина в пикселях для длинной стороны загруженных изображений. По умолчанию равно -1, что означает отсутствие ограничений." +msgstr "" +"Максимальная длина в пикселях для длинной стороны загруженных изображений. " +"По умолчанию равно -1, что означает отсутствие ограничений." -#: ../../mod/admin.php:646 +#: mod/admin.php:916 msgid "JPEG image quality" msgstr "Качество JPEG изображения" -#: ../../mod/admin.php:646 +#: mod/admin.php:916 msgid "" "Uploaded JPEGS will be saved at this quality setting [0-100]. Default is " "100, which is full quality." -msgstr "Загруженные изображения JPEG будут сохранены в этом качестве [0-100]. По умолчанию 100, что означает полное качество." +msgstr "" +"Загруженные изображения JPEG будут сохранены в этом качестве [0-100]. По " +"умолчанию 100, что означает полное качество." -#: ../../mod/admin.php:648 +#: mod/admin.php:918 msgid "Register policy" msgstr "Политика регистрация" -#: ../../mod/admin.php:649 +#: mod/admin.php:919 msgid "Maximum Daily Registrations" msgstr "Максимальное число регистраций в день" -#: ../../mod/admin.php:649 +#: mod/admin.php:919 msgid "" -"If registration is permitted above, this sets the maximum number of new user" -" registrations to accept per day. If register is set to closed, this " -"setting has no effect." -msgstr "Если регистрация разрешена, этот параметр устанавливает максимальное количество новых регистраций пользователей в день. Если регистрация закрыта, эта опция не имеет никакого эффекта." +"If registration is permitted above, this sets the maximum number of new user " +"registrations to accept per day. If register is set to closed, this setting " +"has no effect." +msgstr "" +"Если регистрация разрешена, этот параметр устанавливает максимальное " +"количество новых регистраций пользователей в день. Если регистрация закрыта, " +"эта опция не имеет никакого эффекта." -#: ../../mod/admin.php:650 +#: mod/admin.php:920 msgid "Register text" msgstr "Текст регистрации" -#: ../../mod/admin.php:650 +#: mod/admin.php:920 msgid "Will be displayed prominently on the registration page." msgstr "Будет находиться на видном месте на странице регистрации." -#: ../../mod/admin.php:651 +#: mod/admin.php:921 msgid "Accounts abandoned after x days" msgstr "Аккаунт считается после x дней не воспользованным" -#: ../../mod/admin.php:651 +#: mod/admin.php:921 msgid "" "Will not waste system resources polling external sites for abandonded " "accounts. Enter 0 for no time limit." -msgstr "Не будет тратить ресурсы для опроса сайтов для бесхозных контактов. Введите 0 для отключения лимита времени." +msgstr "" +"Не будет тратить ресурсы для опроса сайтов для бесхозных контактов. Введите " +"0 для отключения лимита времени." -#: ../../mod/admin.php:652 +#: mod/admin.php:922 msgid "Allowed friend domains" msgstr "Разрешенные домены друзей" -#: ../../mod/admin.php:652 +#: mod/admin.php:922 msgid "" "Comma separated list of domains which are allowed to establish friendships " "with this site. Wildcards are accepted. Empty to allow any domains" -msgstr "Разделенный запятыми список доменов, которые разрешены для установления связей. Групповые символы принимаются. Оставьте пустым для разрешения связи со всеми доменами." +msgstr "" +"Разделенный запятыми список доменов, которые разрешены для установления " +"связей. Групповые символы принимаются. Оставьте пустым для разрешения связи " +"со всеми доменами." -#: ../../mod/admin.php:653 +#: mod/admin.php:923 msgid "Allowed email domains" msgstr "Разрешенные почтовые домены" -#: ../../mod/admin.php:653 +#: mod/admin.php:923 msgid "" "Comma separated list of domains which are allowed in email addresses for " "registrations to this site. Wildcards are accepted. Empty to allow any " "domains" -msgstr "Разделенный запятыми список доменов, которые разрешены для установления связей. Групповые символы принимаются. Оставьте пустым для разрешения связи со всеми доменами." +msgstr "" +"Разделенный запятыми список доменов, которые разрешены для установления " +"связей. Групповые символы принимаются. Оставьте пустым для разрешения связи " +"со всеми доменами." -#: ../../mod/admin.php:654 +#: mod/admin.php:924 msgid "Block public" msgstr "Блокировать общественный доступ" -#: ../../mod/admin.php:654 +#: mod/admin.php:924 msgid "" "Check to block public access to all otherwise public personal pages on this " "site unless you are currently logged in." -msgstr "Отметьте, чтобы заблокировать публичный доступ ко всем иным публичным персональным страницам на этом сайте, если вы не вошли на сайт." +msgstr "" +"Отметьте, чтобы заблокировать публичный доступ ко всем иным публичным " +"персональным страницам на этом сайте, если вы не вошли на сайт." -#: ../../mod/admin.php:655 +#: mod/admin.php:925 msgid "Force publish" msgstr "Принудительная публикация" -#: ../../mod/admin.php:655 +#: mod/admin.php:925 msgid "" "Check to force all profiles on this site to be listed in the site directory." -msgstr "Отметьте, чтобы принудительно заставить все профили на этом сайте, быть перечислеными в каталоге сайта." +msgstr "" +"Отметьте, чтобы принудительно заставить все профили на этом сайте, быть " +"перечислеными в каталоге сайта." -#: ../../mod/admin.php:656 -msgid "Global directory update URL" -msgstr "URL обновления глобального каталога" +#: mod/admin.php:926 +msgid "Global directory URL" +msgstr "" -#: ../../mod/admin.php:656 +#: mod/admin.php:926 msgid "" -"URL to update the global directory. If this is not set, the global directory" -" is completely unavailable to the application." -msgstr "URL для обновления глобального каталога. Если он не установлен, глобальный каталог полностью недоступен для приложения." +"URL to the global directory. If this is not set, the global directory is " +"completely unavailable to the application." +msgstr "" -#: ../../mod/admin.php:657 +#: mod/admin.php:927 msgid "Allow threaded items" msgstr "Разрешить темы в обсуждении" -#: ../../mod/admin.php:657 +#: mod/admin.php:927 msgid "Allow infinite level threading for items on this site." msgstr "Разрешить бесконечный уровень для тем на этом сайте." -#: ../../mod/admin.php:658 +#: mod/admin.php:928 msgid "Private posts by default for new users" msgstr "Частные сообщения по умолчанию для новых пользователей" -#: ../../mod/admin.php:658 +#: mod/admin.php:928 msgid "" "Set default post permissions for all new members to the default privacy " "group rather than public." -msgstr "Установить права на создание постов по умолчанию для всех участников в дефолтной приватной группе, а не для публичных участников." +msgstr "" +"Установить права на создание постов по умолчанию для всех участников в " +"дефолтной приватной группе, а не для публичных участников." -#: ../../mod/admin.php:659 +#: mod/admin.php:929 msgid "Don't include post content in email notifications" msgstr "Не включать текст сообщения в email-оповещение." -#: ../../mod/admin.php:659 +#: mod/admin.php:929 msgid "" "Don't include the content of a post/comment/private message/etc. in the " "email notifications that are sent out from this site, as a privacy measure." -msgstr "Не включать содержание сообщения/комментария/личного сообщения и т.д.. в уведомления электронной почты, отправленных с сайта, в качестве меры конфиденциальности." +msgstr "" +"Не включать содержание сообщения/комментария/личного сообщения и т.д.. в " +"уведомления электронной почты, отправленных с сайта, в качестве меры " +"конфиденциальности." -#: ../../mod/admin.php:660 +#: mod/admin.php:930 msgid "Disallow public access to addons listed in the apps menu." msgstr "Запретить публичный доступ к аддонам, перечисленным в меню приложений." -#: ../../mod/admin.php:660 +#: mod/admin.php:930 msgid "" "Checking this box will restrict addons listed in the apps menu to members " "only." -msgstr "При установке этого флажка, будут ограничены аддоны, перечисленные в меню приложений, только для участников." +msgstr "" +"При установке этого флажка, будут ограничены аддоны, перечисленные в меню " +"приложений, только для участников." -#: ../../mod/admin.php:661 +#: mod/admin.php:931 msgid "Don't embed private images in posts" msgstr "Не вставлять личные картинки в постах" -#: ../../mod/admin.php:661 +#: mod/admin.php:931 msgid "" "Don't replace locally-hosted private photos in posts with an embedded copy " "of the image. This means that contacts who receive posts containing private " -"photos will have to authenticate and load each image, which may take a " -"while." -msgstr "Не заменяйте локально расположенные фотографии в постах на внедрённые копии изображений. Это означает, что контакты, которые получают сообщения, содержащие личные фотографии, будут вынуждены идентефицироваться и грузить каждое изображение, что может занять некоторое время." +"photos will have to authenticate and load each image, which may take a while." +msgstr "" +"Не заменяйте локально расположенные фотографии в постах на внедрённые копии " +"изображений. Это означает, что контакты, которые получают сообщения, " +"содержащие личные фотографии, будут вынуждены идентефицироваться и грузить " +"каждое изображение, что может занять некоторое время." -#: ../../mod/admin.php:662 +#: mod/admin.php:932 msgid "Allow Users to set remote_self" msgstr "Разрешить пользователям установить remote_self" -#: ../../mod/admin.php:662 +#: mod/admin.php:932 msgid "" "With checking this, every user is allowed to mark every contact as a " "remote_self in the repair contact dialog. Setting this flag on a contact " "causes mirroring every posting of that contact in the users stream." msgstr "" -#: ../../mod/admin.php:663 +#: mod/admin.php:933 msgid "Block multiple registrations" msgstr "Блокировать множественные регистрации" -#: ../../mod/admin.php:663 +#: mod/admin.php:933 msgid "Disallow users to register additional accounts for use as pages." -msgstr "Запретить пользователям регистрировать дополнительные аккаунты для использования в качестве страниц." +msgstr "" +"Запретить пользователям регистрировать дополнительные аккаунты для " +"использования в качестве страниц." -#: ../../mod/admin.php:664 +#: mod/admin.php:934 msgid "OpenID support" msgstr "Поддержка OpenID" -#: ../../mod/admin.php:664 +#: mod/admin.php:934 msgid "OpenID support for registration and logins." msgstr "OpenID поддержка для регистрации и входа в систему." -#: ../../mod/admin.php:665 +#: mod/admin.php:935 msgid "Fullname check" msgstr "Проверка полного имени" -#: ../../mod/admin.php:665 +#: mod/admin.php:935 msgid "" "Force users to register with a space between firstname and lastname in Full " "name, as an antispam measure" -msgstr "Принудить пользователей регистрироваться с пробелом между именем и фамилией в строке \"полное имя\". Антиспам мера." +msgstr "" +"Принудить пользователей регистрироваться с пробелом между именем и фамилией " +"в строке \"полное имя\". Антиспам мера." -#: ../../mod/admin.php:666 +#: mod/admin.php:936 msgid "UTF-8 Regular expressions" msgstr "UTF-8 регулярные выражения" -#: ../../mod/admin.php:666 +#: mod/admin.php:936 msgid "Use PHP UTF8 regular expressions" msgstr "Используйте PHP UTF-8 для регулярных выражений" -#: ../../mod/admin.php:667 +#: mod/admin.php:937 msgid "Community Page Style" msgstr "" -#: ../../mod/admin.php:667 +#: mod/admin.php:937 msgid "" "Type of community page to show. 'Global community' shows every public " "posting from an open distributed network that arrived on this server." msgstr "" -#: ../../mod/admin.php:668 +#: mod/admin.php:938 msgid "Posts per user on community page" msgstr "" -#: ../../mod/admin.php:668 +#: mod/admin.php:938 msgid "" "The maximum number of posts per user on the community page. (Not valid for " "'Global Community')" msgstr "" -#: ../../mod/admin.php:669 +#: mod/admin.php:939 msgid "Enable OStatus support" msgstr "Включить поддержку OStatus" -#: ../../mod/admin.php:669 +#: mod/admin.php:939 msgid "" "Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All " "communications in OStatus are public, so privacy warnings will be " "occasionally displayed." msgstr "" -#: ../../mod/admin.php:670 +#: mod/admin.php:940 msgid "OStatus conversation completion interval" msgstr "" -#: ../../mod/admin.php:670 +#: mod/admin.php:940 msgid "" "How often shall the poller check for new entries in OStatus conversations? " "This can be a very ressource task." -msgstr "Как часто процессы должны проверять наличие новых записей в OStatus разговорах? Это может быть очень ресурсоёмкой задачей." +msgstr "" +"Как часто процессы должны проверять наличие новых записей в OStatus " +"разговорах? Это может быть очень ресурсоёмкой задачей." -#: ../../mod/admin.php:671 +#: mod/admin.php:941 +msgid "OStatus support can only be enabled if threading is enabled." +msgstr "" + +#: mod/admin.php:943 +msgid "" +"Diaspora support can't be enabled because Friendica was installed into a sub " +"directory." +msgstr "" + +#: mod/admin.php:944 msgid "Enable Diaspora support" msgstr "Включить поддержку Diaspora" -#: ../../mod/admin.php:671 +#: mod/admin.php:944 msgid "Provide built-in Diaspora network compatibility." msgstr "Обеспечить встроенную поддержку сети Diaspora." -#: ../../mod/admin.php:672 +#: mod/admin.php:945 msgid "Only allow Friendica contacts" msgstr "Позвольть только Friendica контакты" -#: ../../mod/admin.php:672 +#: mod/admin.php:945 msgid "" "All contacts must use Friendica protocols. All other built-in communication " "protocols disabled." -msgstr "Все контакты должны использовать только Friendica протоколы. Все другие встроенные коммуникационные протоколы отключены." +msgstr "" +"Все контакты должны использовать только Friendica протоколы. Все другие " +"встроенные коммуникационные протоколы отключены." -#: ../../mod/admin.php:673 +#: mod/admin.php:946 msgid "Verify SSL" msgstr "Проверка SSL" -#: ../../mod/admin.php:673 +#: mod/admin.php:946 msgid "" -"If you wish, you can turn on strict certificate checking. This will mean you" -" cannot connect (at all) to self-signed SSL sites." -msgstr "Если хотите, вы можете включить строгую проверку сертификатов. Это будет означать, что вы не сможете соединиться (вообще) с сайтами, имеющими само-подписанный SSL сертификат." +"If you wish, you can turn on strict certificate checking. This will mean you " +"cannot connect (at all) to self-signed SSL sites." +msgstr "" +"Если хотите, вы можете включить строгую проверку сертификатов. Это будет " +"означать, что вы не сможете соединиться (вообще) с сайтами, имеющими само-" +"подписанный SSL сертификат." -#: ../../mod/admin.php:674 +#: mod/admin.php:947 msgid "Proxy user" msgstr "Прокси пользователь" -#: ../../mod/admin.php:675 +#: mod/admin.php:948 msgid "Proxy URL" msgstr "Прокси URL" -#: ../../mod/admin.php:676 +#: mod/admin.php:949 msgid "Network timeout" msgstr "Тайм-аут сети" -#: ../../mod/admin.php:676 +#: mod/admin.php:949 msgid "Value is in seconds. Set to 0 for unlimited (not recommended)." -msgstr "Значение указывается в секундах. Установите 0 для снятия ограничений (не рекомендуется)." +msgstr "" +"Значение указывается в секундах. Установите 0 для снятия ограничений (не " +"рекомендуется)." -#: ../../mod/admin.php:677 +#: mod/admin.php:950 msgid "Delivery interval" msgstr "Интервал поставки" -#: ../../mod/admin.php:677 +#: mod/admin.php:950 msgid "" "Delay background delivery processes by this many seconds to reduce system " "load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 " "for large dedicated servers." -msgstr "Установите задержку выполнения фоновых процессов доставки до указанного количества секунд, чтобы уменьшить нагрузку на систему. Рекомендация: 4-5 для обычного shared хостинга, 2-3 для виртуальных частных серверов. 0-1 для мощных выделенных серверов." +msgstr "" +"Установите задержку выполнения фоновых процессов доставки до указанного " +"количества секунд, чтобы уменьшить нагрузку на систему. Рекомендация: 4-5 " +"для обычного shared хостинга, 2-3 для виртуальных частных серверов. 0-1 для " +"мощных выделенных серверов." -#: ../../mod/admin.php:678 +#: mod/admin.php:951 msgid "Poll interval" msgstr "Интервал опроса" -#: ../../mod/admin.php:678 +#: mod/admin.php:951 msgid "" "Delay background polling processes by this many seconds to reduce system " "load. If 0, use delivery interval." -msgstr "Установить задержку фоновых процессов опросов путем ограничения количества секунд, чтобы уменьшить нагрузку на систему. Если 0, используется интервал доставки." +msgstr "" +"Установить задержку фоновых процессов опросов путем ограничения количества " +"секунд, чтобы уменьшить нагрузку на систему. Если 0, используется интервал " +"доставки." -#: ../../mod/admin.php:679 +#: mod/admin.php:952 msgid "Maximum Load Average" msgstr "Средняя максимальная нагрузка" -#: ../../mod/admin.php:679 +#: mod/admin.php:952 msgid "" "Maximum system load before delivery and poll processes are deferred - " "default 50." -msgstr "Максимальная нагрузка на систему перед приостановкой процессов доставки и опросов - по умолчанию 50." +msgstr "" +"Максимальная нагрузка на систему перед приостановкой процессов доставки и " +"опросов - по умолчанию 50." -#: ../../mod/admin.php:681 +#: mod/admin.php:953 +msgid "Maximum Load Average (Frontend)" +msgstr "" + +#: mod/admin.php:953 +msgid "Maximum system load before the frontend quits service - default 50." +msgstr "" + +#: mod/admin.php:954 +msgid "Maximum table size for optimization" +msgstr "" + +#: mod/admin.php:954 +msgid "" +"Maximum table size (in MB) for the automatic optimization - default 100 MB. " +"Enter -1 to disable it." +msgstr "" + +#: mod/admin.php:955 +msgid "Minimum level of fragmentation" +msgstr "" + +#: mod/admin.php:955 +msgid "" +"Minimum fragmenation level to start the automatic optimization - default " +"value is 30%." +msgstr "" + +#: mod/admin.php:957 +msgid "Periodical check of global contacts" +msgstr "" + +#: mod/admin.php:957 +msgid "" +"If enabled, the global contacts are checked periodically for missing or " +"outdated data and the vitality of the contacts and servers." +msgstr "" + +#: mod/admin.php:958 +msgid "Days between requery" +msgstr "" + +#: mod/admin.php:958 +msgid "Number of days after which a server is requeried for his contacts." +msgstr "" + +#: mod/admin.php:959 +msgid "Discover contacts from other servers" +msgstr "" + +#: mod/admin.php:959 +msgid "" +"Periodically query other servers for contacts. You can choose between " +"'users': the users on the remote system, 'Global Contacts': active contacts " +"that are known on the system. The fallback is meant for Redmatrix servers " +"and older friendica servers, where global contacts weren't available. The " +"fallback increases the server load, so the recommened setting is 'Users, " +"Global Contacts'." +msgstr "" + +#: mod/admin.php:960 +msgid "Timeframe for fetching global contacts" +msgstr "" + +#: mod/admin.php:960 +msgid "" +"When the discovery is activated, this value defines the timeframe for the " +"activity of the global contacts that are fetched from other servers." +msgstr "" + +#: mod/admin.php:961 +msgid "Search the local directory" +msgstr "" + +#: mod/admin.php:961 +msgid "" +"Search the local directory instead of the global directory. When searching " +"locally, every search will be executed on the global directory in the " +"background. This improves the search results when the search is repeated." +msgstr "" + +#: mod/admin.php:963 +msgid "Publish server information" +msgstr "" + +#: mod/admin.php:963 +msgid "" +"If enabled, general server and usage data will be published. The data " +"contains the name and version of the server, number of users with public " +"profiles, number of posts and the activated protocols and connectors. See <a " +"href='http://the-federation.info/'>the-federation.info</a> for details." +msgstr "" + +#: mod/admin.php:965 msgid "Use MySQL full text engine" msgstr "Использовать систему полнотексного поиска MySQL" -#: ../../mod/admin.php:681 +#: mod/admin.php:965 msgid "" "Activates the full text engine. Speeds up search - but can only search for " "four and more characters." -msgstr "Активизирует систему полнотексного поиска. Ускоряет поиск - но может искать только при указании четырех и более символов." +msgstr "" +"Активизирует систему полнотексного поиска. Ускоряет поиск - но может искать " +"только при указании четырех и более символов." -#: ../../mod/admin.php:682 +#: mod/admin.php:966 msgid "Suppress Language" msgstr "" -#: ../../mod/admin.php:682 +#: mod/admin.php:966 msgid "Suppress language information in meta information about a posting." msgstr "" -#: ../../mod/admin.php:683 +#: mod/admin.php:967 msgid "Suppress Tags" msgstr "" -#: ../../mod/admin.php:683 +#: mod/admin.php:967 msgid "Suppress showing a list of hashtags at the end of the posting." msgstr "" -#: ../../mod/admin.php:684 +#: mod/admin.php:968 msgid "Path to item cache" msgstr "Путь к элементам кэша" -#: ../../mod/admin.php:685 +#: mod/admin.php:968 +msgid "The item caches buffers generated bbcode and external images." +msgstr "" + +#: mod/admin.php:969 msgid "Cache duration in seconds" msgstr "Время жизни кэша в секундах" -#: ../../mod/admin.php:685 +#: mod/admin.php:969 msgid "" -"How long should the cache files be hold? Default value is 86400 seconds (One" -" day). To disable the item cache, set the value to -1." +"How long should the cache files be hold? Default value is 86400 seconds (One " +"day). To disable the item cache, set the value to -1." msgstr "" -#: ../../mod/admin.php:686 +#: mod/admin.php:970 msgid "Maximum numbers of comments per post" msgstr "" -#: ../../mod/admin.php:686 +#: mod/admin.php:970 msgid "How much comments should be shown for each post? Default value is 100." msgstr "" -#: ../../mod/admin.php:687 +#: mod/admin.php:971 msgid "Path for lock file" msgstr "Путь к файлу блокировки" -#: ../../mod/admin.php:688 +#: mod/admin.php:971 +msgid "" +"The lock file is used to avoid multiple pollers at one time. Only define a " +"folder here." +msgstr "" + +#: mod/admin.php:972 msgid "Temp path" msgstr "Временная папка" -#: ../../mod/admin.php:689 +#: mod/admin.php:972 +msgid "" +"If you have a restricted system where the webserver can't access the system " +"temp path, enter another path here." +msgstr "" + +#: mod/admin.php:973 msgid "Base path to installation" msgstr "Путь для установки" -#: ../../mod/admin.php:690 +#: mod/admin.php:973 +msgid "" +"If the system cannot detect the correct path to your installation, enter the " +"correct path here. This setting should only be set if you are using a " +"restricted system and symbolic links to your webroot." +msgstr "" + +#: mod/admin.php:974 msgid "Disable picture proxy" msgstr "" -#: ../../mod/admin.php:690 +#: mod/admin.php:974 msgid "" -"The picture proxy increases performance and privacy. It shouldn't be used on" -" systems with very low bandwith." +"The picture proxy increases performance and privacy. It shouldn't be used on " +"systems with very low bandwith." msgstr "" -#: ../../mod/admin.php:691 +#: mod/admin.php:975 msgid "Enable old style pager" msgstr "" -#: ../../mod/admin.php:691 +#: mod/admin.php:975 msgid "" -"The old style pager has page numbers but slows down massively the page " -"speed." +"The old style pager has page numbers but slows down massively the page speed." msgstr "" -#: ../../mod/admin.php:692 +#: mod/admin.php:976 msgid "Only search in tags" msgstr "" -#: ../../mod/admin.php:692 +#: mod/admin.php:976 msgid "On large systems the text search can slow down the system extremely." msgstr "" -#: ../../mod/admin.php:694 +#: mod/admin.php:978 msgid "New base url" msgstr "Новый базовый url" -#: ../../mod/admin.php:711 +#: mod/admin.php:978 +msgid "" +"Change base url for this server. Sends relocate message to all DFRN contacts " +"of all users." +msgstr "" + +#: mod/admin.php:980 +msgid "RINO Encryption" +msgstr "RINO шифрование" + +#: mod/admin.php:980 +msgid "Encryption layer between nodes." +msgstr "Слой шифрования между узлами." + +#: mod/admin.php:981 +msgid "Embedly API key" +msgstr "" + +#: mod/admin.php:981 +msgid "" +"<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for " +"web pages. This is an optional parameter." +msgstr "" + +#: mod/admin.php:1010 msgid "Update has been marked successful" msgstr "Обновление было успешно отмечено" -#: ../../mod/admin.php:719 +#: mod/admin.php:1018 #, php-format msgid "Database structure update %s was successfully applied." msgstr "" -#: ../../mod/admin.php:722 +#: mod/admin.php:1021 #, php-format msgid "Executing of database structure update %s failed with error: %s" msgstr "" -#: ../../mod/admin.php:734 +#: mod/admin.php:1033 #, php-format msgid "Executing %s failed with error: %s" msgstr "" -#: ../../mod/admin.php:737 +#: mod/admin.php:1036 #, php-format msgid "Update %s was successfully applied." msgstr "Обновление %s успешно применено." -#: ../../mod/admin.php:741 +#: mod/admin.php:1040 #, php-format msgid "Update %s did not return a status. Unknown if it succeeded." -msgstr "Процесс обновления %s не вернул статус. Не известно, выполнено, или нет." +msgstr "" +"Процесс обновления %s не вернул статус. Не известно, выполнено, или нет." -#: ../../mod/admin.php:743 +#: mod/admin.php:1042 #, php-format msgid "There was no additional update function %s that needed to be called." msgstr "" -#: ../../mod/admin.php:762 +#: mod/admin.php:1061 msgid "No failed updates." msgstr "Неудавшихся обновлений нет." -#: ../../mod/admin.php:763 +#: mod/admin.php:1062 msgid "Check database structure" msgstr "" -#: ../../mod/admin.php:768 +#: mod/admin.php:1067 msgid "Failed Updates" msgstr "Неудавшиеся обновления" -#: ../../mod/admin.php:769 +#: mod/admin.php:1068 msgid "" "This does not include updates prior to 1139, which did not return a status." -msgstr "Эта цифра не включает обновления до 1139, которое не возвращает статус." +msgstr "" +"Эта цифра не включает обновления до 1139, которое не возвращает статус." -#: ../../mod/admin.php:770 +#: mod/admin.php:1069 msgid "Mark success (if update was manually applied)" msgstr "Отмечено успешно (если обновление было применено вручную)" -#: ../../mod/admin.php:771 +#: mod/admin.php:1070 msgid "Attempt to execute this update step automatically" msgstr "Попытаться выполнить этот шаг обновления автоматически" -#: ../../mod/admin.php:803 +#: mod/admin.php:1102 #, php-format msgid "" "\n" @@ -2624,7 +3111,7 @@ msgid "" "\t\t\t\tthe administrator of %2$s has set up an account for you." msgstr "" -#: ../../mod/admin.php:806 +#: mod/admin.php:1105 #, php-format msgid "" "\n" @@ -2634,310 +3121,354 @@ msgid "" "\t\t\tLogin Name:\t\t%2$s\n" "\t\t\tPassword:\t\t%3$s\n" "\n" -"\t\t\tYou may change your password from your account \"Settings\" page after logging\n" +"\t\t\tYou may change your password from your account \"Settings\" page after " +"logging\n" "\t\t\tin.\n" "\n" -"\t\t\tPlease take a few moments to review the other account settings on that page.\n" +"\t\t\tPlease take a few moments to review the other account settings on that " +"page.\n" "\n" -"\t\t\tYou may also wish to add some basic information to your default profile\n" +"\t\t\tYou may also wish to add some basic information to your default " +"profile\n" "\t\t\t(on the \"Profiles\" page) so that other people can easily find you.\n" "\n" "\t\t\tWe recommend setting your full name, adding a profile photo,\n" -"\t\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n" -"\t\t\tperhaps what country you live in; if you do not wish to be more specific\n" +"\t\t\tadding some profile \"keywords\" (very useful in making new friends) - " +"and\n" +"\t\t\tperhaps what country you live in; if you do not wish to be more " +"specific\n" "\t\t\tthan that.\n" "\n" -"\t\t\tWe fully respect your right to privacy, and none of these items are necessary.\n" +"\t\t\tWe fully respect your right to privacy, and none of these items are " +"necessary.\n" "\t\t\tIf you are new and do not know anybody here, they may help\n" "\t\t\tyou to make some new and interesting friends.\n" "\n" "\t\t\tThank you and welcome to %4$s." msgstr "" -#: ../../mod/admin.php:838 ../../include/user.php:413 +#: mod/admin.php:1137 include/user.php:423 #, php-format msgid "Registration details for %s" msgstr "Подробности регистрации для %s" -#: ../../mod/admin.php:850 +#: mod/admin.php:1149 #, php-format msgid "%s user blocked/unblocked" msgid_plural "%s users blocked/unblocked" msgstr[0] "%s пользователь заблокирован/разблокирован" msgstr[1] "%s пользователей заблокировано/разблокировано" msgstr[2] "%s пользователей заблокировано/разблокировано" +msgstr[3] "%s пользователей заблокировано/разблокировано" -#: ../../mod/admin.php:857 +#: mod/admin.php:1156 #, php-format msgid "%s user deleted" msgid_plural "%s users deleted" msgstr[0] "%s человек удален" msgstr[1] "%s чел. удалено" msgstr[2] "%s чел. удалено" +msgstr[3] "%s чел. удалено" -#: ../../mod/admin.php:896 +#: mod/admin.php:1203 #, php-format msgid "User '%s' deleted" msgstr "Пользователь '%s' удален" -#: ../../mod/admin.php:904 +#: mod/admin.php:1211 #, php-format msgid "User '%s' unblocked" msgstr "Пользователь '%s' разблокирован" -#: ../../mod/admin.php:904 +#: mod/admin.php:1211 #, php-format msgid "User '%s' blocked" msgstr "Пользователь '%s' блокирован" -#: ../../mod/admin.php:999 +#: mod/admin.php:1302 msgid "Add User" msgstr "Добавить пользователя" -#: ../../mod/admin.php:1000 +#: mod/admin.php:1303 msgid "select all" msgstr "выбрать все" -#: ../../mod/admin.php:1001 +#: mod/admin.php:1304 msgid "User registrations waiting for confirm" msgstr "Регистрации пользователей, ожидающие подтверждения" -#: ../../mod/admin.php:1002 +#: mod/admin.php:1305 msgid "User waiting for permanent deletion" msgstr "Пользователь ожидает окончательного удаления" -#: ../../mod/admin.php:1003 +#: mod/admin.php:1306 msgid "Request date" msgstr "Запрос даты" -#: ../../mod/admin.php:1003 ../../mod/admin.php:1015 ../../mod/admin.php:1016 -#: ../../mod/admin.php:1031 ../../include/contact_selectors.php:79 -#: ../../include/contact_selectors.php:86 +#: mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319 mod/admin.php:1334 +#: include/contact_selectors.php:79 include/contact_selectors.php:86 msgid "Email" msgstr "Эл. почта" -#: ../../mod/admin.php:1004 +#: mod/admin.php:1307 msgid "No registrations." msgstr "Нет регистраций." -#: ../../mod/admin.php:1006 +#: mod/admin.php:1309 msgid "Deny" msgstr "Отклонить" -#: ../../mod/admin.php:1010 +#: mod/admin.php:1313 msgid "Site admin" msgstr "Админ сайта" -#: ../../mod/admin.php:1011 +#: mod/admin.php:1314 msgid "Account expired" msgstr "Аккаунт просрочен" -#: ../../mod/admin.php:1014 +#: mod/admin.php:1317 msgid "New User" msgstr "Новый пользователь" -#: ../../mod/admin.php:1015 ../../mod/admin.php:1016 +#: mod/admin.php:1318 mod/admin.php:1319 msgid "Register date" msgstr "Дата регистрации" -#: ../../mod/admin.php:1015 ../../mod/admin.php:1016 +#: mod/admin.php:1318 mod/admin.php:1319 msgid "Last login" msgstr "Последний вход" -#: ../../mod/admin.php:1015 ../../mod/admin.php:1016 +#: mod/admin.php:1318 mod/admin.php:1319 msgid "Last item" msgstr "Последний пункт" -#: ../../mod/admin.php:1015 +#: mod/admin.php:1318 msgid "Deleted since" msgstr "Удалён с" -#: ../../mod/admin.php:1016 ../../mod/settings.php:36 +#: mod/admin.php:1319 mod/settings.php:41 msgid "Account" msgstr "Аккаунт" -#: ../../mod/admin.php:1018 +#: mod/admin.php:1321 msgid "" "Selected users will be deleted!\\n\\nEverything these users had posted on " "this site will be permanently deleted!\\n\\nAre you sure?" -msgstr "Выбранные пользователи будут удалены!\\n\\nВсе, что эти пользователи написали на этом сайте, будет удалено!\\n\\nВы уверены в вашем действии?" +msgstr "" +"Выбранные пользователи будут удалены!\\n\\nВсе, что эти пользователи " +"написали на этом сайте, будет удалено!\\n\\nВы уверены в вашем действии?" -#: ../../mod/admin.php:1019 +#: mod/admin.php:1322 msgid "" "The user {0} will be deleted!\\n\\nEverything this user has posted on this " "site will be permanently deleted!\\n\\nAre you sure?" -msgstr "Пользователь {0} будет удален!\\n\\nВсе, что этот пользователь написал на этом сайте, будет удалено!\\n\\nВы уверены в вашем действии?" +msgstr "" +"Пользователь {0} будет удален!\\n\\nВсе, что этот пользователь написал на " +"этом сайте, будет удалено!\\n\\nВы уверены в вашем действии?" -#: ../../mod/admin.php:1029 +#: mod/admin.php:1332 msgid "Name of the new user." msgstr "Имя нового пользователя." -#: ../../mod/admin.php:1030 +#: mod/admin.php:1333 msgid "Nickname" msgstr "Ник" -#: ../../mod/admin.php:1030 +#: mod/admin.php:1333 msgid "Nickname of the new user." msgstr "Ник нового пользователя." -#: ../../mod/admin.php:1031 +#: mod/admin.php:1334 msgid "Email address of the new user." msgstr "Email адрес нового пользователя." -#: ../../mod/admin.php:1064 +#: mod/admin.php:1377 #, php-format msgid "Plugin %s disabled." msgstr "Плагин %s отключен." -#: ../../mod/admin.php:1068 +#: mod/admin.php:1381 #, php-format msgid "Plugin %s enabled." msgstr "Плагин %s включен." -#: ../../mod/admin.php:1078 ../../mod/admin.php:1294 +#: mod/admin.php:1392 mod/admin.php:1628 msgid "Disable" msgstr "Отключить" -#: ../../mod/admin.php:1080 ../../mod/admin.php:1296 +#: mod/admin.php:1394 mod/admin.php:1630 msgid "Enable" msgstr "Включить" -#: ../../mod/admin.php:1103 ../../mod/admin.php:1324 +#: mod/admin.php:1417 mod/admin.php:1675 msgid "Toggle" msgstr "Переключить" -#: ../../mod/admin.php:1111 ../../mod/admin.php:1334 +#: mod/admin.php:1425 mod/admin.php:1684 msgid "Author: " msgstr "Автор:" -#: ../../mod/admin.php:1112 ../../mod/admin.php:1335 +#: mod/admin.php:1426 mod/admin.php:1685 msgid "Maintainer: " msgstr "Программа обслуживания: " -#: ../../mod/admin.php:1254 +#: mod/admin.php:1478 +msgid "Reload active plugins" +msgstr "" + +#: mod/admin.php:1483 +#, php-format +msgid "" +"There are currently no plugins available on your node. You can find the " +"official plugin repository at %1$s and might find other interesting plugins " +"in the open plugin registry at %2$s" +msgstr "" + +#: mod/admin.php:1588 msgid "No themes found." msgstr "Темы не найдены." -#: ../../mod/admin.php:1316 +#: mod/admin.php:1666 msgid "Screenshot" msgstr "Скриншот" -#: ../../mod/admin.php:1362 +#: mod/admin.php:1726 +msgid "Reload active themes" +msgstr "" + +#: mod/admin.php:1731 +#, php-format +msgid "No themes found on the system. They should be paced in %1$s" +msgstr "" + +#: mod/admin.php:1732 msgid "[Experimental]" msgstr "[экспериментально]" -#: ../../mod/admin.php:1363 +#: mod/admin.php:1733 msgid "[Unsupported]" msgstr "[Неподдерживаемое]" -#: ../../mod/admin.php:1390 +#: mod/admin.php:1757 msgid "Log settings updated." msgstr "Настройки журнала обновлены." -#: ../../mod/admin.php:1446 +#: mod/admin.php:1794 msgid "Clear" msgstr "Очистить" -#: ../../mod/admin.php:1452 +#: mod/admin.php:1799 msgid "Enable Debugging" msgstr "Включить отладку" -#: ../../mod/admin.php:1453 +#: mod/admin.php:1800 msgid "Log file" msgstr "Лог-файл" -#: ../../mod/admin.php:1453 +#: mod/admin.php:1800 msgid "" "Must be writable by web server. Relative to your Friendica top-level " "directory." -msgstr "Должно быть доступно для записи в веб-сервере. Относительно вашего Friendica каталога верхнего уровня." +msgstr "" +"Должно быть доступно для записи в веб-сервере. Относительно вашего Friendica " +"каталога верхнего уровня." -#: ../../mod/admin.php:1454 +#: mod/admin.php:1801 msgid "Log level" msgstr "Уровень лога" -#: ../../mod/admin.php:1504 -msgid "Close" -msgstr "Закрыть" +#: mod/admin.php:1804 +msgid "PHP logging" +msgstr "PHP логирование" -#: ../../mod/admin.php:1510 -msgid "FTP Host" -msgstr "FTP хост" +#: mod/admin.php:1805 +msgid "" +"To enable logging of PHP errors and warnings you can add the following to " +"the .htconfig.php file of your installation. The filename set in the " +"'error_log' line is relative to the friendica top-level directory and must " +"be writeable by the web server. The option '1' for 'log_errors' and " +"'display_errors' is to enable these options, set to '0' to disable them." +msgstr "" -#: ../../mod/admin.php:1511 -msgid "FTP Path" -msgstr "Путь FTP" +#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759 +msgid "Off" +msgstr "Выкл." -#: ../../mod/admin.php:1512 -msgid "FTP User" -msgstr "FTP пользователь" +#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759 +msgid "On" +msgstr "Вкл." -#: ../../mod/admin.php:1513 -msgid "FTP Password" -msgstr "FTP пароль" +#: mod/admin.php:1932 +#, php-format +msgid "Lock feature %s" +msgstr "" -#: ../../mod/network.php:142 -msgid "Search Results For:" -msgstr "Результаты поиска для:" +#: mod/admin.php:1940 +msgid "Manage Additional Features" +msgstr "" -#: ../../mod/network.php:185 ../../mod/search.php:21 +#: mod/network.php:146 +#, php-format +msgid "Search Results For: %s" +msgstr "" + +#: mod/network.php:191 mod/search.php:25 msgid "Remove term" msgstr "Удалить элемент" -#: ../../mod/network.php:194 ../../mod/search.php:30 -#: ../../include/features.php:42 +#: mod/network.php:200 mod/search.php:34 include/features.php:84 msgid "Saved Searches" msgstr "запомненные поиски" -#: ../../mod/network.php:195 ../../include/group.php:275 +#: mod/network.php:201 include/group.php:293 msgid "add" msgstr "добавить" -#: ../../mod/network.php:356 +#: mod/network.php:365 msgid "Commented Order" -msgstr "Прокомментированный запрос" +msgstr "Последние комментарии" -#: ../../mod/network.php:359 +#: mod/network.php:368 msgid "Sort by Comment Date" msgstr "Сортировать по дате комментария" -#: ../../mod/network.php:362 +#: mod/network.php:373 msgid "Posted Order" -msgstr "Отправленный запрос" +msgstr "Лента записей" -#: ../../mod/network.php:365 +#: mod/network.php:376 msgid "Sort by Post Date" msgstr "Сортировать по дате отправки" -#: ../../mod/network.php:374 +#: mod/network.php:387 msgid "Posts that mention or involve you" msgstr "" -#: ../../mod/network.php:380 +#: mod/network.php:395 msgid "New" msgstr "Новый" -#: ../../mod/network.php:383 +#: mod/network.php:398 msgid "Activity Stream - by date" msgstr "Лента активности - по дате" -#: ../../mod/network.php:389 +#: mod/network.php:406 msgid "Shared Links" msgstr "Ссылки, которыми поделились" -#: ../../mod/network.php:392 +#: mod/network.php:409 msgid "Interesting Links" msgstr "Интересные ссылки" -#: ../../mod/network.php:398 +#: mod/network.php:417 msgid "Starred" msgstr "Помеченный" -#: ../../mod/network.php:401 +#: mod/network.php:420 msgid "Favourite Posts" msgstr "Избранные посты" -#: ../../mod/network.php:463 +#: mod/network.php:479 #, php-format msgid "Warning: This group contains %s member from an insecure network." msgid_plural "" @@ -2945,4152 +3476,4632 @@ msgid_plural "" msgstr[0] "Внимание: Эта группа содержит %s участника с незащищенной сети." msgstr[1] "Внимание: Эта группа содержит %s участников с незащищенной сети." msgstr[2] "Внимание: Эта группа содержит %s участников с незащищенной сети." +msgstr[3] "Внимание: Эта группа содержит %s участников с незащищенной сети." -#: ../../mod/network.php:466 +#: mod/network.php:482 msgid "Private messages to this group are at risk of public disclosure." msgstr "Личные сообщения к этой группе находятся под угрозой обнародования." -#: ../../mod/network.php:520 ../../mod/content.php:119 +#: mod/network.php:549 mod/content.php:119 msgid "No such group" msgstr "Нет такой группы" -#: ../../mod/network.php:537 ../../mod/content.php:130 -msgid "Group is empty" -msgstr "Группа пуста" +#: mod/network.php:580 mod/content.php:135 +#, php-format +msgid "Group: %s" +msgstr "Группа: %s" -#: ../../mod/network.php:544 ../../mod/content.php:134 -msgid "Group: " -msgstr "Группа: " - -#: ../../mod/network.php:554 -msgid "Contact: " -msgstr "Контакт: " - -#: ../../mod/network.php:556 +#: mod/network.php:608 msgid "Private messages to this person are at risk of public disclosure." msgstr "Личные сообщения этому человеку находятся под угрозой обнародования." -#: ../../mod/network.php:561 +#: mod/network.php:613 msgid "Invalid contact." msgstr "Недопустимый контакт." -#: ../../mod/allfriends.php:34 -#, php-format -msgid "Friends of %s" -msgstr "%s Друзья" - -#: ../../mod/allfriends.php:40 +#: mod/allfriends.php:43 msgid "No friends to display." msgstr "Нет друзей." -#: ../../mod/events.php:66 +#: mod/events.php:71 mod/events.php:73 +msgid "Event can not end before it has started." +msgstr "" + +#: mod/events.php:80 mod/events.php:82 msgid "Event title and start time are required." msgstr "Название мероприятия и время начала обязательны для заполнения." -#: ../../mod/events.php:291 +#: mod/events.php:201 +msgid "Sun" +msgstr "Вс" + +#: mod/events.php:202 +msgid "Mon" +msgstr "Пн" + +#: mod/events.php:203 +msgid "Tue" +msgstr "Вт" + +#: mod/events.php:204 +msgid "Wed" +msgstr "Ср" + +#: mod/events.php:205 +msgid "Thu" +msgstr "Чт" + +#: mod/events.php:206 +msgid "Fri" +msgstr "Пт" + +#: mod/events.php:207 +msgid "Sat" +msgstr "Сб" + +#: mod/events.php:208 mod/settings.php:948 include/text.php:1274 +msgid "Sunday" +msgstr "Воскресенье" + +#: mod/events.php:209 mod/settings.php:948 include/text.php:1274 +msgid "Monday" +msgstr "Понедельник" + +#: mod/events.php:210 include/text.php:1274 +msgid "Tuesday" +msgstr "Вторник" + +#: mod/events.php:211 include/text.php:1274 +msgid "Wednesday" +msgstr "Среда" + +#: mod/events.php:212 include/text.php:1274 +msgid "Thursday" +msgstr "Четверг" + +#: mod/events.php:213 include/text.php:1274 +msgid "Friday" +msgstr "Пятница" + +#: mod/events.php:214 include/text.php:1274 +msgid "Saturday" +msgstr "Суббота" + +#: mod/events.php:215 +msgid "Jan" +msgstr "Янв" + +#: mod/events.php:216 +msgid "Feb" +msgstr "Фев" + +#: mod/events.php:217 +msgid "Mar" +msgstr "Мрт" + +#: mod/events.php:218 +msgid "Apr" +msgstr "Апр" + +#: mod/events.php:219 mod/events.php:231 include/text.php:1278 +msgid "May" +msgstr "Май" + +#: mod/events.php:220 +msgid "Jun" +msgstr "Июн" + +#: mod/events.php:221 +msgid "Jul" +msgstr "Июл" + +#: mod/events.php:222 +msgid "Aug" +msgstr "Авг" + +#: mod/events.php:223 +msgid "Sept" +msgstr "Сен" + +#: mod/events.php:224 +msgid "Oct" +msgstr "Окт" + +#: mod/events.php:225 +msgid "Nov" +msgstr "Нбр" + +#: mod/events.php:226 +msgid "Dec" +msgstr "Дек" + +#: mod/events.php:227 include/text.php:1278 +msgid "January" +msgstr "Январь" + +#: mod/events.php:228 include/text.php:1278 +msgid "February" +msgstr "Февраль" + +#: mod/events.php:229 include/text.php:1278 +msgid "March" +msgstr "Март" + +#: mod/events.php:230 include/text.php:1278 +msgid "April" +msgstr "Апрель" + +#: mod/events.php:232 include/text.php:1278 +msgid "June" +msgstr "Июнь" + +#: mod/events.php:233 include/text.php:1278 +msgid "July" +msgstr "Июль" + +#: mod/events.php:234 include/text.php:1278 +msgid "August" +msgstr "Август" + +#: mod/events.php:235 include/text.php:1278 +msgid "September" +msgstr "Сентябрь" + +#: mod/events.php:236 include/text.php:1278 +msgid "October" +msgstr "Октябрь" + +#: mod/events.php:237 include/text.php:1278 +msgid "November" +msgstr "Ноябрь" + +#: mod/events.php:238 include/text.php:1278 +msgid "December" +msgstr "Декабрь" + +#: mod/events.php:239 +msgid "today" +msgstr "сегодня" + +#: mod/events.php:240 include/datetime.php:288 +msgid "month" +msgstr "мес." + +#: mod/events.php:241 include/datetime.php:289 +msgid "week" +msgstr "неделя" + +#: mod/events.php:242 include/datetime.php:290 +msgid "day" +msgstr "день" + +#: mod/events.php:377 msgid "l, F j" msgstr "l, j F" -#: ../../mod/events.php:313 +#: mod/events.php:399 msgid "Edit event" msgstr "Редактировать мероприятие" -#: ../../mod/events.php:335 ../../include/text.php:1647 -#: ../../include/text.php:1657 +#: mod/events.php:421 include/text.php:1728 include/text.php:1735 msgid "link to source" -msgstr "ссылка на источник" +msgstr "ссылка на сообщение" -#: ../../mod/events.php:370 ../../boot.php:2143 ../../include/nav.php:80 -#: ../../view/theme/diabook/theme.php:127 +#: mod/events.php:456 include/identity.php:722 include/nav.php:79 +#: include/nav.php:140 view/theme/diabook/theme.php:127 msgid "Events" msgstr "Мероприятия" -#: ../../mod/events.php:371 +#: mod/events.php:457 msgid "Create New Event" msgstr "Создать новое мероприятие" -#: ../../mod/events.php:372 +#: mod/events.php:458 msgid "Previous" msgstr "Назад" -#: ../../mod/events.php:373 ../../mod/install.php:207 +#: mod/events.php:459 mod/install.php:220 msgid "Next" msgstr "Далее" -#: ../../mod/events.php:446 -msgid "hour:minute" -msgstr "час:минута" - -#: ../../mod/events.php:456 +#: mod/events.php:554 msgid "Event details" msgstr "Сведения о мероприятии" -#: ../../mod/events.php:457 -#, php-format -msgid "Format is %s %s. Starting date and Title are required." -msgstr "Формат %s %s. Необхлдима дата старта и заголовок." +#: mod/events.php:555 +msgid "Starting date and Title are required." +msgstr "" -#: ../../mod/events.php:459 +#: mod/events.php:556 msgid "Event Starts:" msgstr "Начало мероприятия:" -#: ../../mod/events.php:459 ../../mod/events.php:473 +#: mod/events.php:556 mod/events.php:568 msgid "Required" msgstr "Требуется" -#: ../../mod/events.php:462 +#: mod/events.php:558 msgid "Finish date/time is not known or not relevant" msgstr "Дата/время окончания не известны, или не указаны" -#: ../../mod/events.php:464 +#: mod/events.php:560 msgid "Event Finishes:" msgstr "Окончание мероприятия:" -#: ../../mod/events.php:467 +#: mod/events.php:562 msgid "Adjust for viewer timezone" msgstr "Настройка часового пояса" -#: ../../mod/events.php:469 +#: mod/events.php:564 msgid "Description:" msgstr "Описание:" -#: ../../mod/events.php:471 ../../mod/directory.php:136 ../../boot.php:1648 -#: ../../include/bb2diaspora.php:170 ../../include/event.php:40 -msgid "Location:" -msgstr "Откуда:" - -#: ../../mod/events.php:473 +#: mod/events.php:568 msgid "Title:" msgstr "Титул:" -#: ../../mod/events.php:475 +#: mod/events.php:570 msgid "Share this event" msgstr "Поделитесь этим мероприятием" -#: ../../mod/content.php:437 ../../mod/content.php:740 -#: ../../mod/photos.php:1653 ../../object/Item.php:129 -#: ../../include/conversation.php:613 +#: mod/events.php:572 mod/content.php:721 mod/editpost.php:145 +#: mod/photos.php:1631 mod/photos.php:1679 mod/photos.php:1767 +#: object/Item.php:719 include/conversation.php:1216 +msgid "Preview" +msgstr "Предварительный просмотр" + +#: mod/credits.php:16 +msgid "Credits" +msgstr "" + +#: mod/credits.php:17 +msgid "" +"Friendica is a community project, that would not be possible without the " +"help of many people. Here is a list of those who have contributed to the " +"code or the translation of Friendica. Thank you all!" +msgstr "" + +#: mod/content.php:439 mod/content.php:742 mod/photos.php:1722 +#: object/Item.php:133 include/conversation.php:634 msgid "Select" msgstr "Выберите" -#: ../../mod/content.php:471 ../../mod/content.php:852 -#: ../../mod/content.php:853 ../../object/Item.php:326 -#: ../../object/Item.php:327 ../../include/conversation.php:654 +#: mod/content.php:473 mod/content.php:854 mod/content.php:855 +#: object/Item.php:357 object/Item.php:358 include/conversation.php:675 #, php-format msgid "View %s's profile @ %s" msgstr "Просмотреть профиль %s [@ %s]" -#: ../../mod/content.php:481 ../../mod/content.php:864 -#: ../../object/Item.php:340 ../../include/conversation.php:674 +#: mod/content.php:483 mod/content.php:866 object/Item.php:371 +#: include/conversation.php:695 #, php-format msgid "%s from %s" msgstr "%s с %s" -#: ../../mod/content.php:497 ../../include/conversation.php:690 +#: mod/content.php:499 include/conversation.php:711 msgid "View in context" msgstr "Смотреть в контексте" -#: ../../mod/content.php:603 ../../object/Item.php:387 +#: mod/content.php:605 object/Item.php:419 #, php-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "%d комментарий" msgstr[1] "%d комментариев" msgstr[2] "%d комментариев" +msgstr[3] "%d комментариев" -#: ../../mod/content.php:605 ../../object/Item.php:389 -#: ../../object/Item.php:402 ../../include/text.php:1972 +#: mod/content.php:607 object/Item.php:421 object/Item.php:434 +#: include/text.php:2004 msgid "comment" msgid_plural "comments" msgstr[0] "" msgstr[1] "" msgstr[2] "комментарий" +msgstr[3] "комментарий" -#: ../../mod/content.php:606 ../../boot.php:751 ../../object/Item.php:390 -#: ../../include/contact_widgets.php:205 +#: mod/content.php:608 boot.php:870 object/Item.php:422 +#: include/contact_widgets.php:242 include/forums.php:110 +#: include/items.php:5207 view/theme/vier/theme.php:264 msgid "show more" msgstr "показать больше" -#: ../../mod/content.php:620 ../../mod/photos.php:1359 -#: ../../object/Item.php:116 +#: mod/content.php:622 mod/photos.php:1418 object/Item.php:117 msgid "Private Message" msgstr "Личное сообщение" -#: ../../mod/content.php:684 ../../mod/photos.php:1542 -#: ../../object/Item.php:231 +#: mod/content.php:686 mod/photos.php:1607 object/Item.php:253 msgid "I like this (toggle)" msgstr "Нравится" -#: ../../mod/content.php:684 ../../object/Item.php:231 +#: mod/content.php:686 object/Item.php:253 msgid "like" msgstr "нравится" -#: ../../mod/content.php:685 ../../mod/photos.php:1543 -#: ../../object/Item.php:232 +#: mod/content.php:687 mod/photos.php:1608 object/Item.php:254 msgid "I don't like this (toggle)" msgstr "Не нравится" -#: ../../mod/content.php:685 ../../object/Item.php:232 +#: mod/content.php:687 object/Item.php:254 msgid "dislike" msgstr "не нравитса" -#: ../../mod/content.php:687 ../../object/Item.php:234 +#: mod/content.php:689 object/Item.php:256 msgid "Share this" msgstr "Поделитесь этим" -#: ../../mod/content.php:687 ../../object/Item.php:234 +#: mod/content.php:689 object/Item.php:256 msgid "share" msgstr "делиться" -#: ../../mod/content.php:707 ../../mod/photos.php:1562 -#: ../../mod/photos.php:1606 ../../mod/photos.php:1694 -#: ../../object/Item.php:675 +#: mod/content.php:709 mod/photos.php:1627 mod/photos.php:1675 +#: mod/photos.php:1763 object/Item.php:707 msgid "This is you" msgstr "Это вы" -#: ../../mod/content.php:709 ../../mod/photos.php:1564 -#: ../../mod/photos.php:1608 ../../mod/photos.php:1696 ../../boot.php:750 -#: ../../object/Item.php:361 ../../object/Item.php:677 +#: mod/content.php:711 mod/photos.php:1629 mod/photos.php:1677 +#: mod/photos.php:1765 boot.php:869 object/Item.php:393 object/Item.php:709 msgid "Comment" -msgstr "Комментарий" +msgstr "Оставить комментарий" -#: ../../mod/content.php:711 ../../object/Item.php:679 +#: mod/content.php:713 object/Item.php:711 msgid "Bold" msgstr "Жирный" -#: ../../mod/content.php:712 ../../object/Item.php:680 +#: mod/content.php:714 object/Item.php:712 msgid "Italic" msgstr "Kурсивный" -#: ../../mod/content.php:713 ../../object/Item.php:681 +#: mod/content.php:715 object/Item.php:713 msgid "Underline" msgstr "Подчеркнутый" -#: ../../mod/content.php:714 ../../object/Item.php:682 +#: mod/content.php:716 object/Item.php:714 msgid "Quote" msgstr "Цитата" -#: ../../mod/content.php:715 ../../object/Item.php:683 +#: mod/content.php:717 object/Item.php:715 msgid "Code" msgstr "Код" -#: ../../mod/content.php:716 ../../object/Item.php:684 +#: mod/content.php:718 object/Item.php:716 msgid "Image" msgstr "Изображение / Фото" -#: ../../mod/content.php:717 ../../object/Item.php:685 +#: mod/content.php:719 object/Item.php:717 msgid "Link" msgstr "Ссылка" -#: ../../mod/content.php:718 ../../object/Item.php:686 +#: mod/content.php:720 object/Item.php:718 msgid "Video" msgstr "Видео" -#: ../../mod/content.php:719 ../../mod/editpost.php:145 -#: ../../mod/photos.php:1566 ../../mod/photos.php:1610 -#: ../../mod/photos.php:1698 ../../object/Item.php:687 -#: ../../include/conversation.php:1126 -msgid "Preview" -msgstr "предварительный просмотр" - -#: ../../mod/content.php:728 ../../mod/settings.php:676 -#: ../../object/Item.php:120 +#: mod/content.php:730 mod/settings.php:721 object/Item.php:122 +#: object/Item.php:124 msgid "Edit" msgstr "Редактировать" -#: ../../mod/content.php:753 ../../object/Item.php:195 +#: mod/content.php:755 object/Item.php:217 msgid "add star" msgstr "пометить" -#: ../../mod/content.php:754 ../../object/Item.php:196 +#: mod/content.php:756 object/Item.php:218 msgid "remove star" msgstr "убрать метку" -#: ../../mod/content.php:755 ../../object/Item.php:197 +#: mod/content.php:757 object/Item.php:219 msgid "toggle star status" msgstr "переключить статус" -#: ../../mod/content.php:758 ../../object/Item.php:200 +#: mod/content.php:760 object/Item.php:222 msgid "starred" msgstr "помечено" -#: ../../mod/content.php:759 ../../object/Item.php:220 +#: mod/content.php:761 object/Item.php:242 msgid "add tag" msgstr "добавить ключевое слово (таг)" -#: ../../mod/content.php:763 ../../object/Item.php:133 +#: mod/content.php:765 object/Item.php:137 msgid "save to folder" msgstr "сохранить в папке" -#: ../../mod/content.php:854 ../../object/Item.php:328 +#: mod/content.php:856 object/Item.php:359 msgid "to" msgstr "к" -#: ../../mod/content.php:855 ../../object/Item.php:330 +#: mod/content.php:857 object/Item.php:361 msgid "Wall-to-Wall" msgstr "Стена-на-Стену" -#: ../../mod/content.php:856 ../../object/Item.php:331 +#: mod/content.php:858 object/Item.php:362 msgid "via Wall-To-Wall:" msgstr "через Стена-на-Стену:" -#: ../../mod/removeme.php:46 ../../mod/removeme.php:49 +#: mod/removeme.php:46 mod/removeme.php:49 msgid "Remove My Account" msgstr "Удалить мой аккаунт" -#: ../../mod/removeme.php:47 +#: mod/removeme.php:47 msgid "" "This will completely remove your account. Once this has been done it is not " "recoverable." -msgstr "Это позволит полностью удалить ваш аккаунт. Как только это будет сделано, аккаунт восстановлению не подлежит." +msgstr "" +"Это позволит полностью удалить ваш аккаунт. Как только это будет сделано, " +"аккаунт восстановлению не подлежит." -#: ../../mod/removeme.php:48 +#: mod/removeme.php:48 msgid "Please enter your password for verification:" msgstr "Пожалуйста, введите свой пароль для проверки:" -#: ../../mod/install.php:117 +#: mod/install.php:128 msgid "Friendica Communications Server - Setup" msgstr "Коммуникационный сервер Friendica - Доступ" -#: ../../mod/install.php:123 +#: mod/install.php:134 msgid "Could not connect to database." msgstr "Не удалось подключиться к базе данных." -#: ../../mod/install.php:127 +#: mod/install.php:138 msgid "Could not create table." msgstr "Не удалось создать таблицу." -#: ../../mod/install.php:133 +#: mod/install.php:144 msgid "Your Friendica site database has been installed." msgstr "База данных сайта установлена." -#: ../../mod/install.php:138 +#: mod/install.php:149 msgid "" "You may need to import the file \"database.sql\" manually using phpmyadmin " "or mysql." -msgstr "Вам может понадобиться импортировать файл \"database.sql\" вручную с помощью PhpMyAdmin или MySQL." +msgstr "" +"Вам может понадобиться импортировать файл \"database.sql\" вручную с помощью " +"PhpMyAdmin или MySQL." -#: ../../mod/install.php:139 ../../mod/install.php:206 -#: ../../mod/install.php:525 +#: mod/install.php:150 mod/install.php:219 mod/install.php:577 msgid "Please see the file \"INSTALL.txt\"." msgstr "Пожалуйста, смотрите файл \"INSTALL.txt\"." -#: ../../mod/install.php:203 +#: mod/install.php:162 +msgid "Database already in use." +msgstr "" + +#: mod/install.php:216 msgid "System check" msgstr "Проверить систему" -#: ../../mod/install.php:208 +#: mod/install.php:221 msgid "Check again" msgstr "Проверить еще раз" -#: ../../mod/install.php:227 +#: mod/install.php:240 msgid "Database connection" msgstr "Подключение к базе данных" -#: ../../mod/install.php:228 +#: mod/install.php:241 msgid "" "In order to install Friendica we need to know how to connect to your " "database." -msgstr "Для того, чтобы установить Friendica, мы должны знать, как подключиться к базе данных." +msgstr "" +"Для того, чтобы установить Friendica, мы должны знать, как подключиться к " +"базе данных." -#: ../../mod/install.php:229 +#: mod/install.php:242 msgid "" "Please contact your hosting provider or site administrator if you have " "questions about these settings." -msgstr "Пожалуйста, свяжитесь с вашим хостинг-провайдером или администратором сайта, если у вас есть вопросы об этих параметрах." +msgstr "" +"Пожалуйста, свяжитесь с вашим хостинг-провайдером или администратором сайта, " +"если у вас есть вопросы об этих параметрах." -#: ../../mod/install.php:230 +#: mod/install.php:243 msgid "" "The database you specify below should already exist. If it does not, please " "create it before continuing." -msgstr "Базы данных, указанная ниже, должна уже существовать. Если этого нет, пожалуйста, создайте ее перед продолжением." +msgstr "" +"Базы данных, указанная ниже, должна уже существовать. Если этого нет, " +"пожалуйста, создайте ее перед продолжением." -#: ../../mod/install.php:234 +#: mod/install.php:247 msgid "Database Server Name" msgstr "Имя сервера базы данных" -#: ../../mod/install.php:235 +#: mod/install.php:248 msgid "Database Login Name" msgstr "Логин базы данных" -#: ../../mod/install.php:236 +#: mod/install.php:249 msgid "Database Login Password" msgstr "Пароль базы данных" -#: ../../mod/install.php:237 +#: mod/install.php:250 msgid "Database Name" msgstr "Имя базы данных" -#: ../../mod/install.php:238 ../../mod/install.php:277 +#: mod/install.php:251 mod/install.php:290 msgid "Site administrator email address" msgstr "Адрес электронной почты администратора сайта" -#: ../../mod/install.php:238 ../../mod/install.php:277 +#: mod/install.php:251 mod/install.php:290 msgid "" "Your account email address must match this in order to use the web admin " "panel." -msgstr "Ваш адрес электронной почты аккаунта должен соответствовать этому, чтобы использовать веб-панель администратора." +msgstr "" +"Ваш адрес электронной почты аккаунта должен соответствовать этому, чтобы " +"использовать веб-панель администратора." -#: ../../mod/install.php:242 ../../mod/install.php:280 +#: mod/install.php:255 mod/install.php:293 msgid "Please select a default timezone for your website" msgstr "Пожалуйста, выберите часовой пояс по умолчанию для вашего сайта" -#: ../../mod/install.php:267 +#: mod/install.php:280 msgid "Site settings" msgstr "Настройки сайта" -#: ../../mod/install.php:321 +#: mod/install.php:334 msgid "Could not find a command line version of PHP in the web server PATH." msgstr "Не удалось найти PATH веб-сервера в установках PHP." -#: ../../mod/install.php:322 +#: mod/install.php:335 msgid "" "If you don't have a command line version of PHP installed on server, you " -"will not be able to run background polling via cron. See <a " -"href='http://friendica.com/node/27'>'Activating scheduled tasks'</a>" -msgstr "Если на вашем сервере не установлена версия командной строки PHP, вы не будете иметь возможность запускать фоновые опросы через крон. См. <a href='http://friendica.com/node/27'> 'Активация запланированных задачах' </a>" +"will not be able to run background polling via cron. See <a href='https://" +"github.com/friendica/friendica/blob/master/doc/Install.md#set-up-the-" +"poller'>'Setup the poller'</a>" +msgstr "" -#: ../../mod/install.php:326 +#: mod/install.php:339 msgid "PHP executable path" msgstr "PHP executable path" -#: ../../mod/install.php:326 +#: mod/install.php:339 msgid "" "Enter full path to php executable. You can leave this blank to continue the " "installation." -msgstr "Введите полный путь к исполняемому файлу PHP. Вы можете оставить это поле пустым, чтобы продолжить установку." +msgstr "" +"Введите полный путь к исполняемому файлу PHP. Вы можете оставить это поле " +"пустым, чтобы продолжить установку." -#: ../../mod/install.php:331 +#: mod/install.php:344 msgid "Command line PHP" msgstr "Command line PHP" -#: ../../mod/install.php:340 +#: mod/install.php:353 msgid "PHP executable is not the php cli binary (could be cgi-fgci version)" msgstr "" -#: ../../mod/install.php:341 +#: mod/install.php:354 msgid "Found PHP version: " msgstr "Найденная PHP версия: " -#: ../../mod/install.php:343 +#: mod/install.php:356 msgid "PHP cli binary" msgstr "PHP cli binary" -#: ../../mod/install.php:354 +#: mod/install.php:367 msgid "" "The command line version of PHP on your system does not have " "\"register_argc_argv\" enabled." msgstr "Не включено \"register_argc_argv\" в установках PHP." -#: ../../mod/install.php:355 +#: mod/install.php:368 msgid "This is required for message delivery to work." msgstr "Это необходимо для работы доставки сообщений." -#: ../../mod/install.php:357 +#: mod/install.php:370 msgid "PHP register_argc_argv" msgstr "PHP register_argc_argv" -#: ../../mod/install.php:378 +#: mod/install.php:391 msgid "" "Error: the \"openssl_pkey_new\" function on this system is not able to " "generate encryption keys" -msgstr "Ошибка: функция \"openssl_pkey_new\" в этой системе не в состоянии генерировать ключи шифрования" +msgstr "" +"Ошибка: функция \"openssl_pkey_new\" в этой системе не в состоянии " +"генерировать ключи шифрования" -#: ../../mod/install.php:379 +#: mod/install.php:392 msgid "" -"If running under Windows, please see " -"\"http://www.php.net/manual/en/openssl.installation.php\"." -msgstr "Если вы работаете под Windows, см. \"http://www.php.net/manual/en/openssl.installation.php\"." +"If running under Windows, please see \"http://www.php.net/manual/en/openssl." +"installation.php\"." +msgstr "" +"Если вы работаете под Windows, см. \"http://www.php.net/manual/en/openssl." +"installation.php\"." -#: ../../mod/install.php:381 +#: mod/install.php:394 msgid "Generate encryption keys" msgstr "Генерация шифрованых ключей" -#: ../../mod/install.php:388 +#: mod/install.php:401 msgid "libCurl PHP module" msgstr "libCurl PHP модуль" -#: ../../mod/install.php:389 +#: mod/install.php:402 msgid "GD graphics PHP module" msgstr "GD graphics PHP модуль" -#: ../../mod/install.php:390 +#: mod/install.php:403 msgid "OpenSSL PHP module" msgstr "OpenSSL PHP модуль" -#: ../../mod/install.php:391 +#: mod/install.php:404 msgid "mysqli PHP module" msgstr "mysqli PHP модуль" -#: ../../mod/install.php:392 +#: mod/install.php:405 msgid "mb_string PHP module" msgstr "mb_string PHP модуль" -#: ../../mod/install.php:397 ../../mod/install.php:399 +#: mod/install.php:406 +msgid "mcrypt PHP module" +msgstr "" + +#: mod/install.php:411 mod/install.php:413 msgid "Apache mod_rewrite module" msgstr "Apache mod_rewrite module" -#: ../../mod/install.php:397 +#: mod/install.php:411 msgid "" "Error: Apache webserver mod-rewrite module is required but not installed." -msgstr "Ошибка: необходим модуль веб-сервера Apache mod-rewrite, но он не установлен." +msgstr "" +"Ошибка: необходим модуль веб-сервера Apache mod-rewrite, но он не установлен." -#: ../../mod/install.php:405 +#: mod/install.php:419 msgid "Error: libCURL PHP module required but not installed." msgstr "Ошибка: необходим libCURL PHP модуль, но он не установлен." -#: ../../mod/install.php:409 +#: mod/install.php:423 msgid "" "Error: GD graphics PHP module with JPEG support required but not installed." -msgstr "Ошибка: необходим PHP модуль GD графики с поддержкой JPEG, но он не установлен." +msgstr "" +"Ошибка: необходим PHP модуль GD графики с поддержкой JPEG, но он не " +"установлен." -#: ../../mod/install.php:413 +#: mod/install.php:427 msgid "Error: openssl PHP module required but not installed." msgstr "Ошибка: необходим PHP модуль OpenSSL, но он не установлен." -#: ../../mod/install.php:417 +#: mod/install.php:431 msgid "Error: mysqli PHP module required but not installed." msgstr "Ошибка: необходим PHP модуль MySQLi, но он не установлен." -#: ../../mod/install.php:421 +#: mod/install.php:435 msgid "Error: mb_string PHP module required but not installed." msgstr "Ошибка: необходим PHP модуль mb_string, но он не установлен." -#: ../../mod/install.php:438 -msgid "" -"The web installer needs to be able to create a file called \".htconfig.php\"" -" in the top folder of your web server and it is unable to do so." -msgstr "Веб-инсталлятору требуется создать файл с именем \". htconfig.php\" в верхней папке веб-сервера, но он не в состоянии это сделать." +#: mod/install.php:439 +msgid "Error: mcrypt PHP module required but not installed." +msgstr "" -#: ../../mod/install.php:439 +#: mod/install.php:451 +msgid "" +"Function mcrypt_create_iv() is not defined. This is needed to enable RINO2 " +"encryption layer." +msgstr "" + +#: mod/install.php:453 +msgid "mcrypt_create_iv() function" +msgstr "" + +#: mod/install.php:469 +msgid "" +"The web installer needs to be able to create a file called \".htconfig.php\" " +"in the top folder of your web server and it is unable to do so." +msgstr "" +"Веб-инсталлятору требуется создать файл с именем \". htconfig.php\" в " +"верхней папке веб-сервера, но он не в состоянии это сделать." + +#: mod/install.php:470 msgid "" "This is most often a permission setting, as the web server may not be able " "to write files in your folder - even if you can." -msgstr "Это наиболее частые параметры разрешений, когда веб-сервер не может записать файлы в папке - даже если вы можете." +msgstr "" +"Это наиболее частые параметры разрешений, когда веб-сервер не может записать " +"файлы в папке - даже если вы можете." -#: ../../mod/install.php:440 +#: mod/install.php:471 msgid "" "At the end of this procedure, we will give you a text to save in a file " "named .htconfig.php in your Friendica top folder." -msgstr "В конце этой процедуры, мы дадим вам текст, для сохранения в файле с именем .htconfig.php в корневой папке, где установлена Friendica." +msgstr "" +"В конце этой процедуры, мы дадим вам текст, для сохранения в файле с именем ." +"htconfig.php в корневой папке, где установлена Friendica." -#: ../../mod/install.php:441 +#: mod/install.php:472 msgid "" -"You can alternatively skip this procedure and perform a manual installation." -" Please see the file \"INSTALL.txt\" for instructions." -msgstr "В качестве альтернативы вы можете пропустить эту процедуру и выполнить установку вручную. Пожалуйста, обратитесь к файлу \"INSTALL.txt\" для получения инструкций." +"You can alternatively skip this procedure and perform a manual installation. " +"Please see the file \"INSTALL.txt\" for instructions." +msgstr "" +"В качестве альтернативы вы можете пропустить эту процедуру и выполнить " +"установку вручную. Пожалуйста, обратитесь к файлу \"INSTALL.txt\" для " +"получения инструкций." -#: ../../mod/install.php:444 +#: mod/install.php:475 msgid ".htconfig.php is writable" msgstr ".htconfig.php is writable" -#: ../../mod/install.php:454 +#: mod/install.php:485 msgid "" "Friendica uses the Smarty3 template engine to render its web views. Smarty3 " "compiles templates to PHP to speed up rendering." -msgstr "Friendica использует механизм шаблонов Smarty3 для генерации веб-страниц. Smarty3 компилирует шаблоны в PHP для увеличения скорости загрузки." +msgstr "" +"Friendica использует механизм шаблонов Smarty3 для генерации веб-страниц. " +"Smarty3 компилирует шаблоны в PHP для увеличения скорости загрузки." -#: ../../mod/install.php:455 +#: mod/install.php:486 msgid "" "In order to store these compiled templates, the web server needs to have " "write access to the directory view/smarty3/ under the Friendica top level " "folder." -msgstr "Для того чтобы хранить эти скомпилированные шаблоны, веб-сервер должен иметь доступ на запись для папки view/smarty3 в директории, где установлена Friendica." +msgstr "" +"Для того чтобы хранить эти скомпилированные шаблоны, веб-сервер должен иметь " +"доступ на запись для папки view/smarty3 в директории, где установлена " +"Friendica." -#: ../../mod/install.php:456 +#: mod/install.php:487 msgid "" -"Please ensure that the user that your web server runs as (e.g. www-data) has" -" write access to this folder." -msgstr "Пожалуйста, убедитесь, что пользователь, под которым работает ваш веб-сервер (например www-data), имеет доступ на запись в этой папке." +"Please ensure that the user that your web server runs as (e.g. www-data) has " +"write access to this folder." +msgstr "" +"Пожалуйста, убедитесь, что пользователь, под которым работает ваш веб-сервер " +"(например www-data), имеет доступ на запись в этой папке." -#: ../../mod/install.php:457 +#: mod/install.php:488 msgid "" "Note: as a security measure, you should give the web server write access to " "view/smarty3/ only--not the template files (.tpl) that it contains." -msgstr "Примечание: в качестве меры безопасности, вы должны дать вебсерверу доступ на запись только в view/smarty3 - но не на сами файлы шаблонов (.tpl)., Которые содержатся в этой папке." +msgstr "" +"Примечание: в качестве меры безопасности, вы должны дать вебсерверу доступ " +"на запись только в view/smarty3 - но не на сами файлы шаблонов (.tpl)., " +"Которые содержатся в этой папке." -#: ../../mod/install.php:460 +#: mod/install.php:491 msgid "view/smarty3 is writable" msgstr "view/smarty3 доступен для записи" -#: ../../mod/install.php:472 +#: mod/install.php:507 msgid "" "Url rewrite in .htaccess is not working. Check your server configuration." -msgstr "Url rewrite в .htaccess не работает. Проверьте конфигурацию вашего сервера.." +msgstr "" +"Url rewrite в .htaccess не работает. Проверьте конфигурацию вашего сервера.." -#: ../../mod/install.php:474 +#: mod/install.php:509 msgid "Url rewrite is working" msgstr "Url rewrite работает" -#: ../../mod/install.php:484 +#: mod/install.php:526 +msgid "ImageMagick PHP extension is installed" +msgstr "" + +#: mod/install.php:528 +msgid "ImageMagick supports GIF" +msgstr "" + +#: mod/install.php:536 msgid "" "The database configuration file \".htconfig.php\" could not be written. " "Please use the enclosed text to create a configuration file in your web " "server root." -msgstr "Файл конфигурации базы данных \".htconfig.php\" не могла быть записан. Пожалуйста, используйте приложенный текст, чтобы создать конфигурационный файл в корневом каталоге веб-сервера." +msgstr "" +"Файл конфигурации базы данных \".htconfig.php\" не могла быть записан. " +"Пожалуйста, используйте приложенный текст, чтобы создать конфигурационный " +"файл в корневом каталоге веб-сервера." -#: ../../mod/install.php:523 +#: mod/install.php:575 msgid "<h1>What next</h1>" msgstr "<h1>Что далее</h1>" -#: ../../mod/install.php:524 +#: mod/install.php:576 msgid "" -"IMPORTANT: You will need to [manually] setup a scheduled task for the " -"poller." -msgstr "ВАЖНО: Вам нужно будет [вручную] установить запланированное задание для регистратора." +"IMPORTANT: You will need to [manually] setup a scheduled task for the poller." +msgstr "" +"ВАЖНО: Вам нужно будет [вручную] установить запланированное задание для " +"регистратора." -#: ../../mod/wallmessage.php:42 ../../mod/wallmessage.php:112 +#: mod/wallmessage.php:42 mod/wallmessage.php:112 #, php-format msgid "Number of daily wall messages for %s exceeded. Message failed." -msgstr "Количество ежедневных сообщений на стене %s превышено. Сообщение отменено.." +msgstr "" +"Количество ежедневных сообщений на стене %s превышено. Сообщение отменено.." -#: ../../mod/wallmessage.php:59 +#: mod/wallmessage.php:59 msgid "Unable to check your home location." msgstr "Невозможно проверить местоположение." -#: ../../mod/wallmessage.php:86 ../../mod/wallmessage.php:95 +#: mod/wallmessage.php:86 mod/wallmessage.php:95 msgid "No recipient." msgstr "Без адресата." -#: ../../mod/wallmessage.php:143 +#: mod/wallmessage.php:143 #, php-format msgid "" "If you wish for %s to respond, please check that the privacy settings on " "your site allow private mail from unknown senders." -msgstr "Если Вы хотите ответить %s, пожалуйста, проверьте, позволяют ли настройки конфиденциальности на Вашем сайте принимать персональную почту от неизвестных отправителей." +msgstr "" +"Если Вы хотите ответить %s, пожалуйста, проверьте, позволяют ли настройки " +"конфиденциальности на Вашем сайте принимать персональную почту от " +"неизвестных отправителей." -#: ../../mod/help.php:79 +#: mod/help.php:41 msgid "Help:" msgstr "Помощь:" -#: ../../mod/help.php:84 ../../include/nav.php:114 +#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:302 msgid "Help" msgstr "Помощь" -#: ../../mod/help.php:90 ../../index.php:256 +#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:270 msgid "Not Found" msgstr "Не найдено" -#: ../../mod/help.php:93 ../../index.php:259 +#: mod/help.php:56 index.php:273 msgid "Page not found." msgstr "Страница не найдена." -#: ../../mod/dfrn_poll.php:103 ../../mod/dfrn_poll.php:536 +#: mod/dfrn_poll.php:103 mod/dfrn_poll.php:536 #, php-format msgid "%1$s welcomes %2$s" msgstr "%1$s добро пожаловать %2$s" -#: ../../mod/home.php:35 +#: mod/home.php:35 #, php-format msgid "Welcome to %s" msgstr "Добро пожаловать на %s!" -#: ../../mod/wall_attach.php:75 +#: mod/wall_attach.php:94 msgid "Sorry, maybe your upload is bigger than the PHP configuration allows" msgstr "" -#: ../../mod/wall_attach.php:75 +#: mod/wall_attach.php:94 msgid "Or - did you try to upload an empty file?" msgstr "" -#: ../../mod/wall_attach.php:81 +#: mod/wall_attach.php:105 #, php-format -msgid "File exceeds size limit of %d" -msgstr "Файл превышает предельный размер %d" +msgid "File exceeds size limit of %s" +msgstr "" -#: ../../mod/wall_attach.php:122 ../../mod/wall_attach.php:133 +#: mod/wall_attach.php:156 mod/wall_attach.php:172 msgid "File upload failed." msgstr "Загрузка файла не удалась." -#: ../../mod/match.php:12 -msgid "Profile Match" -msgstr "Похожие профили" - -#: ../../mod/match.php:20 +#: mod/match.php:33 msgid "No keywords to match. Please add keywords to your default profile." -msgstr "Нет соответствующих ключевых слов. Пожалуйста, добавьте ключевые слова для вашего профиля по умолчанию." +msgstr "" +"Нет соответствующих ключевых слов. Пожалуйста, добавьте ключевые слова для " +"вашего профиля по умолчанию." -#: ../../mod/match.php:57 +#: mod/match.php:84 msgid "is interested in:" msgstr "интересуется:" -#: ../../mod/match.php:58 ../../mod/suggest.php:90 ../../boot.php:1568 -#: ../../include/contact_widgets.php:10 -msgid "Connect" -msgstr "Подключить" +#: mod/match.php:98 +msgid "Profile Match" +msgstr "Похожие профили" -#: ../../mod/share.php:44 +#: mod/share.php:38 msgid "link" msgstr "ссылка" -#: ../../mod/community.php:23 +#: mod/community.php:27 msgid "Not available." msgstr "Недоступно." -#: ../../mod/community.php:32 ../../include/nav.php:129 -#: ../../include/nav.php:131 ../../view/theme/diabook/theme.php:129 +#: mod/community.php:36 include/nav.php:136 include/nav.php:138 +#: view/theme/diabook/theme.php:129 msgid "Community" msgstr "Сообщество" -#: ../../mod/community.php:62 ../../mod/community.php:71 -#: ../../mod/search.php:168 ../../mod/search.php:192 +#: mod/community.php:66 mod/community.php:75 mod/search.php:228 msgid "No results." msgstr "Нет результатов." -#: ../../mod/settings.php:29 ../../mod/photos.php:80 +#: mod/settings.php:34 mod/photos.php:117 msgid "everybody" msgstr "каждый" -#: ../../mod/settings.php:41 -msgid "Additional features" -msgstr "Дополнительные возможности" - -#: ../../mod/settings.php:46 +#: mod/settings.php:58 msgid "Display" -msgstr "" +msgstr "Внешний вид" -#: ../../mod/settings.php:52 ../../mod/settings.php:780 +#: mod/settings.php:65 mod/settings.php:864 msgid "Social Networks" -msgstr "" +msgstr "Социальные сети" -#: ../../mod/settings.php:62 ../../include/nav.php:170 +#: mod/settings.php:79 include/nav.php:180 msgid "Delegations" -msgstr "" +msgstr "Делегирование" -#: ../../mod/settings.php:67 +#: mod/settings.php:86 msgid "Connected apps" msgstr "Подключенные приложения" -#: ../../mod/settings.php:72 ../../mod/uexport.php:85 +#: mod/settings.php:93 mod/uexport.php:85 msgid "Export personal data" msgstr "Экспорт личных данных" -#: ../../mod/settings.php:77 +#: mod/settings.php:100 msgid "Remove account" msgstr "Удалить аккаунт" -#: ../../mod/settings.php:129 +#: mod/settings.php:153 msgid "Missing some important data!" msgstr "Не хватает важных данных!" -#: ../../mod/settings.php:238 +#: mod/settings.php:266 msgid "Failed to connect with email account using the settings provided." -msgstr "Не удалось подключиться к аккаунту e-mail, используя указанные настройки." +msgstr "" +"Не удалось подключиться к аккаунту e-mail, используя указанные настройки." -#: ../../mod/settings.php:243 +#: mod/settings.php:271 msgid "Email settings updated." msgstr "Настройки эл. почты обновлены." -#: ../../mod/settings.php:258 +#: mod/settings.php:286 msgid "Features updated" msgstr "Настройки обновлены" -#: ../../mod/settings.php:321 +#: mod/settings.php:353 msgid "Relocate message has been send to your contacts" msgstr "Перемещённое сообщение было отправлено списку контактов" -#: ../../mod/settings.php:335 +#: mod/settings.php:367 include/user.php:39 msgid "Passwords do not match. Password unchanged." msgstr "Пароли не совпадают. Пароль не изменен." -#: ../../mod/settings.php:340 +#: mod/settings.php:372 msgid "Empty passwords are not allowed. Password unchanged." msgstr "Пустые пароли не допускаются. Пароль не изменен." -#: ../../mod/settings.php:348 +#: mod/settings.php:380 msgid "Wrong password." msgstr "Неверный пароль." -#: ../../mod/settings.php:359 +#: mod/settings.php:391 msgid "Password changed." msgstr "Пароль изменен." -#: ../../mod/settings.php:361 +#: mod/settings.php:393 msgid "Password update failed. Please try again." msgstr "Обновление пароля не удалось. Пожалуйста, попробуйте еще раз." -#: ../../mod/settings.php:428 +#: mod/settings.php:462 msgid " Please use a shorter name." msgstr " Пожалуйста, используйте более короткое имя." -#: ../../mod/settings.php:430 +#: mod/settings.php:464 msgid " Name too short." msgstr " Имя слишком короткое." -#: ../../mod/settings.php:439 +#: mod/settings.php:473 msgid "Wrong Password" msgstr "Неверный пароль." -#: ../../mod/settings.php:444 +#: mod/settings.php:478 msgid " Not valid email." msgstr " Неверный e-mail." -#: ../../mod/settings.php:450 +#: mod/settings.php:484 msgid " Cannot change to that email." msgstr " Невозможно изменить на этот e-mail." -#: ../../mod/settings.php:506 +#: mod/settings.php:540 msgid "Private forum has no privacy permissions. Using default privacy group." -msgstr "Частный форум не имеет настроек приватности. Используется группа конфиденциальности по умолчанию." +msgstr "" +"Частный форум не имеет настроек приватности. Используется группа " +"конфиденциальности по умолчанию." -#: ../../mod/settings.php:510 +#: mod/settings.php:544 msgid "Private forum has no privacy permissions and no default privacy group." -msgstr "Частный форум не имеет настроек приватности и не имеет групп приватности по умолчанию." +msgstr "" +"Частный форум не имеет настроек приватности и не имеет групп приватности по " +"умолчанию." -#: ../../mod/settings.php:540 +#: mod/settings.php:583 msgid "Settings updated." msgstr "Настройки обновлены." -#: ../../mod/settings.php:613 ../../mod/settings.php:639 -#: ../../mod/settings.php:675 +#: mod/settings.php:658 mod/settings.php:684 mod/settings.php:720 msgid "Add application" msgstr "Добавить приложения" -#: ../../mod/settings.php:617 ../../mod/settings.php:643 +#: mod/settings.php:662 mod/settings.php:688 msgid "Consumer Key" msgstr "Consumer Key" -#: ../../mod/settings.php:618 ../../mod/settings.php:644 +#: mod/settings.php:663 mod/settings.php:689 msgid "Consumer Secret" msgstr "Consumer Secret" -#: ../../mod/settings.php:619 ../../mod/settings.php:645 +#: mod/settings.php:664 mod/settings.php:690 msgid "Redirect" msgstr "Перенаправление" -#: ../../mod/settings.php:620 ../../mod/settings.php:646 +#: mod/settings.php:665 mod/settings.php:691 msgid "Icon url" msgstr "URL символа" -#: ../../mod/settings.php:631 +#: mod/settings.php:676 msgid "You can't edit this application." msgstr "Вы не можете изменить это приложение." -#: ../../mod/settings.php:674 +#: mod/settings.php:719 msgid "Connected Apps" msgstr "Подключенные приложения" -#: ../../mod/settings.php:678 +#: mod/settings.php:723 msgid "Client key starts with" msgstr "Ключ клиента начинается с" -#: ../../mod/settings.php:679 +#: mod/settings.php:724 msgid "No name" msgstr "Нет имени" -#: ../../mod/settings.php:680 +#: mod/settings.php:725 msgid "Remove authorization" msgstr "Удалить авторизацию" -#: ../../mod/settings.php:692 +#: mod/settings.php:737 msgid "No Plugin settings configured" msgstr "Нет сконфигурированных настроек плагина" -#: ../../mod/settings.php:700 +#: mod/settings.php:745 msgid "Plugin Settings" msgstr "Настройки плагина" -#: ../../mod/settings.php:714 -msgid "Off" -msgstr "" - -#: ../../mod/settings.php:714 -msgid "On" -msgstr "" - -#: ../../mod/settings.php:722 +#: mod/settings.php:767 msgid "Additional Features" msgstr "Дополнительные возможности" -#: ../../mod/settings.php:736 ../../mod/settings.php:737 +#: mod/settings.php:777 mod/settings.php:781 +msgid "General Social Media Settings" +msgstr "" + +#: mod/settings.php:787 +msgid "Disable intelligent shortening" +msgstr "" + +#: mod/settings.php:789 +msgid "" +"Normally the system tries to find the best link to add to shortened posts. " +"If this option is enabled then every shortened post will always point to the " +"original friendica post." +msgstr "" + +#: mod/settings.php:795 +msgid "Automatically follow any GNU Social (OStatus) followers/mentioners" +msgstr "" + +#: mod/settings.php:797 +msgid "" +"If you receive a message from an unknown OStatus user, this option decides " +"what to do. If it is checked, a new contact will be created for every " +"unknown user." +msgstr "" + +#: mod/settings.php:806 +msgid "Your legacy GNU Social account" +msgstr "" + +#: mod/settings.php:808 +msgid "" +"If you enter your old GNU Social/Statusnet account name here (in the format " +"user@domain.tld), your contacts will be added automatically. The field will " +"be emptied when done." +msgstr "" + +#: mod/settings.php:811 +msgid "Repair OStatus subscriptions" +msgstr "" + +#: mod/settings.php:820 mod/settings.php:821 #, php-format msgid "Built-in support for %s connectivity is %s" msgstr "Встроенная поддержка для %s подключение %s" -#: ../../mod/settings.php:736 ../../mod/dfrn_request.php:838 -#: ../../include/contact_selectors.php:80 +#: mod/settings.php:820 mod/dfrn_request.php:865 +#: include/contact_selectors.php:80 msgid "Diaspora" msgstr "Diaspora" -#: ../../mod/settings.php:736 ../../mod/settings.php:737 +#: mod/settings.php:820 mod/settings.php:821 msgid "enabled" msgstr "подключено" -#: ../../mod/settings.php:736 ../../mod/settings.php:737 +#: mod/settings.php:820 mod/settings.php:821 msgid "disabled" msgstr "отключено" -#: ../../mod/settings.php:737 -msgid "StatusNet" -msgstr "StatusNet" +#: mod/settings.php:821 +msgid "GNU Social (OStatus)" +msgstr "" -#: ../../mod/settings.php:773 +#: mod/settings.php:857 msgid "Email access is disabled on this site." msgstr "Доступ эл. почты отключен на этом сайте." -#: ../../mod/settings.php:785 +#: mod/settings.php:869 msgid "Email/Mailbox Setup" msgstr "Настройка эл. почты / почтового ящика" -#: ../../mod/settings.php:786 +#: mod/settings.php:870 msgid "" "If you wish to communicate with email contacts using this service " "(optional), please specify how to connect to your mailbox." -msgstr "Если вы хотите общаться с Email контактами, используя этот сервис (по желанию), пожалуйста, уточните, как подключиться к вашему почтовому ящику." +msgstr "" +"Если вы хотите общаться с Email контактами, используя этот сервис (по " +"желанию), пожалуйста, уточните, как подключиться к вашему почтовому ящику." -#: ../../mod/settings.php:787 +#: mod/settings.php:871 msgid "Last successful email check:" msgstr "Последняя успешная проверка электронной почты:" -#: ../../mod/settings.php:789 +#: mod/settings.php:873 msgid "IMAP server name:" msgstr "Имя IMAP сервера:" -#: ../../mod/settings.php:790 +#: mod/settings.php:874 msgid "IMAP port:" msgstr "Порт IMAP:" -#: ../../mod/settings.php:791 +#: mod/settings.php:875 msgid "Security:" msgstr "Безопасность:" -#: ../../mod/settings.php:791 ../../mod/settings.php:796 +#: mod/settings.php:875 mod/settings.php:880 msgid "None" msgstr "Ничего" -#: ../../mod/settings.php:792 +#: mod/settings.php:876 msgid "Email login name:" msgstr "Логин эл. почты:" -#: ../../mod/settings.php:793 +#: mod/settings.php:877 msgid "Email password:" msgstr "Пароль эл. почты:" -#: ../../mod/settings.php:794 +#: mod/settings.php:878 msgid "Reply-to address:" msgstr "Адрес для ответа:" -#: ../../mod/settings.php:795 +#: mod/settings.php:879 msgid "Send public posts to all email contacts:" msgstr "Отправлять открытые сообщения на все контакты электронной почты:" -#: ../../mod/settings.php:796 +#: mod/settings.php:880 msgid "Action after import:" msgstr "Действие после импорта:" -#: ../../mod/settings.php:796 +#: mod/settings.php:880 msgid "Mark as seen" msgstr "Отметить, как прочитанное" -#: ../../mod/settings.php:796 +#: mod/settings.php:880 msgid "Move to folder" msgstr "Переместить в папку" -#: ../../mod/settings.php:797 +#: mod/settings.php:881 msgid "Move to folder:" msgstr "Переместить в папку:" -#: ../../mod/settings.php:878 +#: mod/settings.php:967 msgid "Display Settings" msgstr "Параметры дисплея" -#: ../../mod/settings.php:884 ../../mod/settings.php:899 +#: mod/settings.php:973 mod/settings.php:991 msgid "Display Theme:" msgstr "Показать тему:" -#: ../../mod/settings.php:885 +#: mod/settings.php:974 msgid "Mobile Theme:" msgstr "Мобильная тема:" -#: ../../mod/settings.php:886 +#: mod/settings.php:975 msgid "Update browser every xx seconds" msgstr "Обновление браузера каждые хх секунд" -#: ../../mod/settings.php:886 -msgid "Minimum of 10 seconds, no maximum" -msgstr "Минимум 10 секунд, максимума нет" +#: mod/settings.php:975 +msgid "Minimum of 10 seconds. Enter -1 to disable it." +msgstr "" -#: ../../mod/settings.php:887 +#: mod/settings.php:976 msgid "Number of items to display per page:" msgstr "Количество элементов, отображаемых на одной странице:" -#: ../../mod/settings.php:887 ../../mod/settings.php:888 +#: mod/settings.php:976 mod/settings.php:977 msgid "Maximum of 100 items" msgstr "Максимум 100 элементов" -#: ../../mod/settings.php:888 +#: mod/settings.php:977 msgid "Number of items to display per page when viewed from mobile device:" -msgstr "Количество элементов на странице, когда просмотр осуществляется с мобильных устройств:" +msgstr "" +"Количество элементов на странице, когда просмотр осуществляется с мобильных " +"устройств:" -#: ../../mod/settings.php:889 +#: mod/settings.php:978 msgid "Don't show emoticons" msgstr "не показывать emoticons" -#: ../../mod/settings.php:890 +#: mod/settings.php:979 +msgid "Calendar" +msgstr "" + +#: mod/settings.php:980 +msgid "Beginning of week:" +msgstr "" + +#: mod/settings.php:981 msgid "Don't show notices" msgstr "" -#: ../../mod/settings.php:891 +#: mod/settings.php:982 msgid "Infinite scroll" msgstr "Бесконечная прокрутка" -#: ../../mod/settings.php:892 +#: mod/settings.php:983 msgid "Automatic updates only at the top of the network page" msgstr "" -#: ../../mod/settings.php:969 +#: mod/settings.php:985 view/theme/cleanzero/config.php:82 +#: view/theme/dispy/config.php:72 view/theme/quattro/config.php:66 +#: view/theme/diabook/config.php:150 view/theme/vier/config.php:109 +#: view/theme/duepuntozero/config.php:61 +msgid "Theme settings" +msgstr "Настройки темы" + +#: mod/settings.php:1062 msgid "User Types" msgstr "" -#: ../../mod/settings.php:970 +#: mod/settings.php:1063 msgid "Community Types" msgstr "" -#: ../../mod/settings.php:971 +#: mod/settings.php:1064 msgid "Normal Account Page" msgstr "Стандартная страница аккаунта" -#: ../../mod/settings.php:972 +#: mod/settings.php:1065 msgid "This account is a normal personal profile" msgstr "Этот аккаунт является обычным персональным профилем" -#: ../../mod/settings.php:975 +#: mod/settings.php:1068 msgid "Soapbox Page" msgstr "" -#: ../../mod/settings.php:976 +#: mod/settings.php:1069 msgid "Automatically approve all connection/friend requests as read-only fans" -msgstr "Автоматически одобряются все подключения / запросы в друзья, \"только для чтения\" поклонниками" +msgstr "" +"Автоматически одобряются все подключения / запросы в друзья, \"только для " +"чтения\" поклонниками" -#: ../../mod/settings.php:979 +#: mod/settings.php:1072 msgid "Community Forum/Celebrity Account" msgstr "Аккаунт сообщества Форум/Знаменитость" -#: ../../mod/settings.php:980 -msgid "" -"Automatically approve all connection/friend requests as read-write fans" -msgstr "Автоматически одобряются все подключения / запросы в друзья, \"для чтения и записей\" поклонников" +#: mod/settings.php:1073 +msgid "Automatically approve all connection/friend requests as read-write fans" +msgstr "" +"Автоматически одобряются все подключения / запросы в друзья, \"для чтения и " +"записей\" поклонников" -#: ../../mod/settings.php:983 +#: mod/settings.php:1076 msgid "Automatic Friend Page" msgstr "\"Автоматический друг\" страница" -#: ../../mod/settings.php:984 +#: mod/settings.php:1077 msgid "Automatically approve all connection/friend requests as friends" -msgstr "Автоматически одобряются все подключения / запросы в друзья, расширяется список друзей" +msgstr "" +"Автоматически одобряются все подключения / запросы в друзья, расширяется " +"список друзей" -#: ../../mod/settings.php:987 +#: mod/settings.php:1080 msgid "Private Forum [Experimental]" msgstr "Личный форум [экспериментально]" -#: ../../mod/settings.php:988 +#: mod/settings.php:1081 msgid "Private forum - approved members only" msgstr "Приватный форум - разрешено только участникам" -#: ../../mod/settings.php:1000 +#: mod/settings.php:1093 msgid "OpenID:" msgstr "OpenID:" -#: ../../mod/settings.php:1000 +#: mod/settings.php:1093 msgid "(Optional) Allow this OpenID to login to this account." msgstr "(Необязательно) Разрешить этому OpenID входить в этот аккаунт" -#: ../../mod/settings.php:1010 +#: mod/settings.php:1103 msgid "Publish your default profile in your local site directory?" -msgstr "Публиковать ваш профиль по умолчанию в вашем локальном каталоге на сайте?" +msgstr "" +"Публиковать ваш профиль по умолчанию в вашем локальном каталоге на сайте?" -#: ../../mod/settings.php:1010 ../../mod/settings.php:1016 -#: ../../mod/settings.php:1024 ../../mod/settings.php:1028 -#: ../../mod/settings.php:1033 ../../mod/settings.php:1039 -#: ../../mod/settings.php:1045 ../../mod/settings.php:1051 -#: ../../mod/settings.php:1081 ../../mod/settings.php:1082 -#: ../../mod/settings.php:1083 ../../mod/settings.php:1084 -#: ../../mod/settings.php:1085 ../../mod/dfrn_request.php:830 -#: ../../mod/register.php:234 ../../mod/profiles.php:661 -#: ../../mod/profiles.php:665 ../../mod/api.php:106 -msgid "No" -msgstr "Нет" - -#: ../../mod/settings.php:1016 +#: mod/settings.php:1109 msgid "Publish your default profile in the global social directory?" msgstr "Публиковать ваш профиль по умолчанию в глобальном социальном каталоге?" -#: ../../mod/settings.php:1024 +#: mod/settings.php:1117 msgid "Hide your contact/friend list from viewers of your default profile?" -msgstr "Скрывать ваш список контактов/друзей от посетителей вашего профиля по умолчанию?" +msgstr "" +"Скрывать ваш список контактов/друзей от посетителей вашего профиля по " +"умолчанию?" -#: ../../mod/settings.php:1028 ../../include/conversation.php:1057 +#: mod/settings.php:1121 include/acl_selectors.php:331 msgid "Hide your profile details from unknown viewers?" msgstr "Скрыть данные профиля из неизвестных зрителей?" -#: ../../mod/settings.php:1028 +#: mod/settings.php:1121 msgid "" "If enabled, posting public messages to Diaspora and other networks isn't " "possible." msgstr "" -#: ../../mod/settings.php:1033 +#: mod/settings.php:1126 msgid "Allow friends to post to your profile page?" msgstr "Разрешить друзьям оставлять сообщения на страницу вашего профиля?" -#: ../../mod/settings.php:1039 +#: mod/settings.php:1132 msgid "Allow friends to tag your posts?" msgstr "Разрешить друзьям отмечять ваши сообщения?" -#: ../../mod/settings.php:1045 +#: mod/settings.php:1138 msgid "Allow us to suggest you as a potential friend to new members?" msgstr "Позвольть предлогать Вам потенциальных друзей?" -#: ../../mod/settings.php:1051 +#: mod/settings.php:1144 msgid "Permit unknown people to send you private mail?" msgstr "Разрешить незнакомым людям отправлять вам личные сообщения?" -#: ../../mod/settings.php:1059 +#: mod/settings.php:1152 msgid "Profile is <strong>not published</strong>." msgstr "Профиль <strong>не публикуется</strong>." -#: ../../mod/settings.php:1067 -msgid "Your Identity Address is" -msgstr "Ваш идентификационный адрес" +#: mod/settings.php:1160 +#, php-format +msgid "Your Identity Address is <strong>'%s'</strong> or '%s'." +msgstr "" -#: ../../mod/settings.php:1078 +#: mod/settings.php:1167 msgid "Automatically expire posts after this many days:" msgstr "Автоматическое истекание срока действия сообщения после стольких дней:" -#: ../../mod/settings.php:1078 +#: mod/settings.php:1167 msgid "If empty, posts will not expire. Expired posts will be deleted" -msgstr "Если пусто, срок действия сообщений не будет ограничен. Сообщения с истекшим сроком действия будут удалены" +msgstr "" +"Если пусто, срок действия сообщений не будет ограничен. Сообщения с истекшим " +"сроком действия будут удалены" -#: ../../mod/settings.php:1079 +#: mod/settings.php:1168 msgid "Advanced expiration settings" msgstr "Настройки расширенного окончания срока действия" -#: ../../mod/settings.php:1080 +#: mod/settings.php:1169 msgid "Advanced Expiration" msgstr "Расширенное окончание срока действия" -#: ../../mod/settings.php:1081 +#: mod/settings.php:1170 msgid "Expire posts:" msgstr "Срок хранения сообщений:" -#: ../../mod/settings.php:1082 +#: mod/settings.php:1171 msgid "Expire personal notes:" msgstr "Срок хранения личных заметок:" -#: ../../mod/settings.php:1083 +#: mod/settings.php:1172 msgid "Expire starred posts:" msgstr "Срок хранения усеянных сообщений:" -#: ../../mod/settings.php:1084 +#: mod/settings.php:1173 msgid "Expire photos:" msgstr "Срок хранения фотографий:" -#: ../../mod/settings.php:1085 +#: mod/settings.php:1174 msgid "Only expire posts by others:" msgstr "Только устаревшие посты других:" -#: ../../mod/settings.php:1111 +#: mod/settings.php:1202 msgid "Account Settings" msgstr "Настройки аккаунта" -#: ../../mod/settings.php:1119 +#: mod/settings.php:1210 msgid "Password Settings" -msgstr "Настройка пароля" +msgstr "Смена пароля" -#: ../../mod/settings.php:1120 +#: mod/settings.php:1211 mod/register.php:274 msgid "New Password:" msgstr "Новый пароль:" -#: ../../mod/settings.php:1121 +#: mod/settings.php:1212 mod/register.php:275 msgid "Confirm:" msgstr "Подтвердите:" -#: ../../mod/settings.php:1121 +#: mod/settings.php:1212 msgid "Leave password fields blank unless changing" msgstr "Оставьте поля пароля пустыми, если он не изменяется" -#: ../../mod/settings.php:1122 +#: mod/settings.php:1213 msgid "Current Password:" msgstr "Текущий пароль:" -#: ../../mod/settings.php:1122 ../../mod/settings.php:1123 +#: mod/settings.php:1213 mod/settings.php:1214 msgid "Your current password to confirm the changes" msgstr "Ваш текущий пароль, для подтверждения изменений" -#: ../../mod/settings.php:1123 +#: mod/settings.php:1214 msgid "Password:" msgstr "Пароль:" -#: ../../mod/settings.php:1127 +#: mod/settings.php:1218 msgid "Basic Settings" msgstr "Основные параметры" -#: ../../mod/settings.php:1128 ../../include/profile_advanced.php:15 +#: mod/settings.php:1219 include/identity.php:588 msgid "Full Name:" msgstr "Полное имя:" -#: ../../mod/settings.php:1129 +#: mod/settings.php:1220 msgid "Email Address:" msgstr "Адрес электронной почты:" -#: ../../mod/settings.php:1130 +#: mod/settings.php:1221 msgid "Your Timezone:" msgstr "Ваш часовой пояс:" -#: ../../mod/settings.php:1131 +#: mod/settings.php:1222 +msgid "Your Language:" +msgstr "" + +#: mod/settings.php:1222 +msgid "" +"Set the language we use to show you friendica interface and to send you " +"emails" +msgstr "" + +#: mod/settings.php:1223 msgid "Default Post Location:" msgstr "Местонахождение по умолчанию:" -#: ../../mod/settings.php:1132 +#: mod/settings.php:1224 msgid "Use Browser Location:" msgstr "Использовать определение местоположения браузером:" -#: ../../mod/settings.php:1135 +#: mod/settings.php:1227 msgid "Security and Privacy Settings" msgstr "Параметры безопасности и конфиденциальности" -#: ../../mod/settings.php:1137 +#: mod/settings.php:1229 msgid "Maximum Friend Requests/Day:" msgstr "Максимум запросов в друзья в день:" -#: ../../mod/settings.php:1137 ../../mod/settings.php:1167 +#: mod/settings.php:1229 mod/settings.php:1259 msgid "(to prevent spam abuse)" msgstr "(для предотвращения спама)" -#: ../../mod/settings.php:1138 +#: mod/settings.php:1230 msgid "Default Post Permissions" msgstr "Разрешение на сообщения по умолчанию" -#: ../../mod/settings.php:1139 +#: mod/settings.php:1231 msgid "(click to open/close)" msgstr "(нажмите, чтобы открыть / закрыть)" -#: ../../mod/settings.php:1148 ../../mod/photos.php:1146 -#: ../../mod/photos.php:1519 +#: mod/settings.php:1240 mod/photos.php:1199 mod/photos.php:1584 msgid "Show to Groups" msgstr "Показать в группах" -#: ../../mod/settings.php:1149 ../../mod/photos.php:1147 -#: ../../mod/photos.php:1520 +#: mod/settings.php:1241 mod/photos.php:1200 mod/photos.php:1585 msgid "Show to Contacts" msgstr "Показывать контактам" -#: ../../mod/settings.php:1150 +#: mod/settings.php:1242 msgid "Default Private Post" msgstr "Личное сообщение по умолчанию" -#: ../../mod/settings.php:1151 +#: mod/settings.php:1243 msgid "Default Public Post" msgstr "Публичное сообщение по умолчанию" -#: ../../mod/settings.php:1155 +#: mod/settings.php:1247 msgid "Default Permissions for New Posts" msgstr "Права для новых записей по умолчанию" -#: ../../mod/settings.php:1167 +#: mod/settings.php:1259 msgid "Maximum private messages per day from unknown people:" msgstr "Максимальное количество личных сообщений от незнакомых людей в день:" -#: ../../mod/settings.php:1170 +#: mod/settings.php:1262 msgid "Notification Settings" msgstr "Настройка уведомлений" -#: ../../mod/settings.php:1171 +#: mod/settings.php:1263 msgid "By default post a status message when:" msgstr "Отправить состояние о статусе по умолчанию, если:" -#: ../../mod/settings.php:1172 +#: mod/settings.php:1264 msgid "accepting a friend request" msgstr "принятие запроса на добавление в друзья" -#: ../../mod/settings.php:1173 +#: mod/settings.php:1265 msgid "joining a forum/community" msgstr "вступление в сообщество/форум" -#: ../../mod/settings.php:1174 +#: mod/settings.php:1266 msgid "making an <em>interesting</em> profile change" msgstr "сделать изменения в <em>настройках интересов</em> профиля" -#: ../../mod/settings.php:1175 +#: mod/settings.php:1267 msgid "Send a notification email when:" msgstr "Отправлять уведомление по электронной почте, когда:" -#: ../../mod/settings.php:1176 +#: mod/settings.php:1268 msgid "You receive an introduction" msgstr "Вы получили запрос" -#: ../../mod/settings.php:1177 +#: mod/settings.php:1269 msgid "Your introductions are confirmed" msgstr "Ваши запросы подтверждены" -#: ../../mod/settings.php:1178 +#: mod/settings.php:1270 msgid "Someone writes on your profile wall" msgstr "Кто-то пишет на стене вашего профиля" -#: ../../mod/settings.php:1179 +#: mod/settings.php:1271 msgid "Someone writes a followup comment" msgstr "Кто-то пишет последующий комментарий" -#: ../../mod/settings.php:1180 +#: mod/settings.php:1272 msgid "You receive a private message" msgstr "Вы получаете личное сообщение" -#: ../../mod/settings.php:1181 +#: mod/settings.php:1273 msgid "You receive a friend suggestion" msgstr "Вы полулили предложение о добавлении в друзья" -#: ../../mod/settings.php:1182 +#: mod/settings.php:1274 msgid "You are tagged in a post" msgstr "Вы отмечены в посте" -#: ../../mod/settings.php:1183 +#: mod/settings.php:1275 msgid "You are poked/prodded/etc. in a post" msgstr "" -#: ../../mod/settings.php:1185 +#: mod/settings.php:1277 +msgid "Activate desktop notifications" +msgstr "" + +#: mod/settings.php:1277 +msgid "Show desktop popup on new notifications" +msgstr "" + +#: mod/settings.php:1279 msgid "Text-only notification emails" msgstr "" -#: ../../mod/settings.php:1187 +#: mod/settings.php:1281 msgid "Send text only notification emails, without the html part" msgstr "" -#: ../../mod/settings.php:1189 +#: mod/settings.php:1283 msgid "Advanced Account/Page Type Settings" -msgstr "Расширенные настройки типа аккаунта/страницы" +msgstr "Расширенные настройки учётной записи" -#: ../../mod/settings.php:1190 +#: mod/settings.php:1284 msgid "Change the behaviour of this account for special situations" msgstr "Измените поведение этого аккаунта в специальных ситуациях" -#: ../../mod/settings.php:1193 +#: mod/settings.php:1287 msgid "Relocate" -msgstr "Переместить" +msgstr "Перемещение" -#: ../../mod/settings.php:1194 +#: mod/settings.php:1288 msgid "" "If you have moved this profile from another server, and some of your " "contacts don't receive your updates, try pushing this button." -msgstr "Если вы переместили эту анкету с другого сервера, и некоторые из ваших контактов не получили ваши обновления, попробуйте нажать эту кнопку." +msgstr "" +"Если вы переместили эту анкету с другого сервера, и некоторые из ваших " +"контактов не получили ваши обновления, попробуйте нажать эту кнопку." -#: ../../mod/settings.php:1195 +#: mod/settings.php:1289 msgid "Resend relocate message to contacts" msgstr "Отправить перемещённые сообщения контактам" -#: ../../mod/dfrn_request.php:95 +#: mod/dfrn_request.php:96 msgid "This introduction has already been accepted." msgstr "Этот запрос был уже принят." -#: ../../mod/dfrn_request.php:120 ../../mod/dfrn_request.php:518 +#: mod/dfrn_request.php:119 mod/dfrn_request.php:516 msgid "Profile location is not valid or does not contain profile information." -msgstr "Местоположение профиля является недопустимым или не содержит информацию о профиле." +msgstr "" +"Местоположение профиля является недопустимым или не содержит информацию о " +"профиле." -#: ../../mod/dfrn_request.php:125 ../../mod/dfrn_request.php:523 +#: mod/dfrn_request.php:124 mod/dfrn_request.php:521 msgid "Warning: profile location has no identifiable owner name." -msgstr "Внимание: местоположение профиля не имеет идентифицируемого имени владельца." +msgstr "" +"Внимание: местоположение профиля не имеет идентифицируемого имени владельца." -#: ../../mod/dfrn_request.php:127 ../../mod/dfrn_request.php:525 +#: mod/dfrn_request.php:126 mod/dfrn_request.php:523 msgid "Warning: profile location has no profile photo." msgstr "Внимание: местоположение профиля не имеет еще фотографии профиля." -#: ../../mod/dfrn_request.php:130 ../../mod/dfrn_request.php:528 +#: mod/dfrn_request.php:129 mod/dfrn_request.php:526 #, php-format msgid "%d required parameter was not found at the given location" msgid_plural "%d required parameters were not found at the given location" msgstr[0] "%d требуемый параметр не был найден в заданном месте" msgstr[1] "%d требуемых параметров не были найдены в заданном месте" msgstr[2] "%d требуемых параметров не были найдены в заданном месте" +msgstr[3] "%d требуемых параметров не были найдены в заданном месте" -#: ../../mod/dfrn_request.php:172 +#: mod/dfrn_request.php:172 msgid "Introduction complete." msgstr "Запрос создан." -#: ../../mod/dfrn_request.php:214 +#: mod/dfrn_request.php:214 msgid "Unrecoverable protocol error." msgstr "Неисправимая ошибка протокола." -#: ../../mod/dfrn_request.php:242 +#: mod/dfrn_request.php:242 msgid "Profile unavailable." msgstr "Профиль недоступен." -#: ../../mod/dfrn_request.php:267 +#: mod/dfrn_request.php:267 #, php-format msgid "%s has received too many connection requests today." msgstr "К %s пришло сегодня слишком много запросов на подключение." -#: ../../mod/dfrn_request.php:268 +#: mod/dfrn_request.php:268 msgid "Spam protection measures have been invoked." msgstr "Были применены меры защиты от спама." -#: ../../mod/dfrn_request.php:269 +#: mod/dfrn_request.php:269 msgid "Friends are advised to please try again in 24 hours." msgstr "Друзья советуют попробовать еще раз в ближайшие 24 часа." -#: ../../mod/dfrn_request.php:331 +#: mod/dfrn_request.php:331 msgid "Invalid locator" msgstr "Недопустимый локатор" -#: ../../mod/dfrn_request.php:340 +#: mod/dfrn_request.php:340 msgid "Invalid email address." msgstr "Неверный адрес электронной почты." -#: ../../mod/dfrn_request.php:367 +#: mod/dfrn_request.php:367 msgid "This account has not been configured for email. Request failed." msgstr "Этот аккаунт не настроен для электронной почты. Запрос не удался." -#: ../../mod/dfrn_request.php:463 -msgid "Unable to resolve your name at the provided location." -msgstr "Не удается установить ваше имя на предложенном местоположении." - -#: ../../mod/dfrn_request.php:476 +#: mod/dfrn_request.php:474 msgid "You have already introduced yourself here." msgstr "Вы уже ввели информацию о себе здесь." -#: ../../mod/dfrn_request.php:480 +#: mod/dfrn_request.php:478 #, php-format msgid "Apparently you are already friends with %s." msgstr "Похоже, что вы уже друзья с %s." -#: ../../mod/dfrn_request.php:501 +#: mod/dfrn_request.php:499 msgid "Invalid profile URL." msgstr "Неверный URL профиля." -#: ../../mod/dfrn_request.php:507 ../../include/follow.php:27 +#: mod/dfrn_request.php:505 include/follow.php:72 msgid "Disallowed profile URL." msgstr "Запрещенный URL профиля." -#: ../../mod/dfrn_request.php:597 +#: mod/dfrn_request.php:596 msgid "Your introduction has been sent." msgstr "Ваш запрос отправлен." -#: ../../mod/dfrn_request.php:650 +#: mod/dfrn_request.php:636 +msgid "" +"Remote subscription can't be done for your network. Please subscribe " +"directly on your system." +msgstr "" + +#: mod/dfrn_request.php:659 msgid "Please login to confirm introduction." msgstr "Для подтверждения запроса войдите пожалуйста с паролем." -#: ../../mod/dfrn_request.php:660 +#: mod/dfrn_request.php:669 msgid "" -"Incorrect identity currently logged in. Please login to " -"<strong>this</strong> profile." -msgstr "Неверно идентифицирован вход. Пожалуйста, войдите в <strong>этот</strong> профиль." +"Incorrect identity currently logged in. Please login to <strong>this</" +"strong> profile." +msgstr "" +"Неверно идентифицирован вход. Пожалуйста, войдите в <strong>этот</strong> " +"профиль." -#: ../../mod/dfrn_request.php:671 +#: mod/dfrn_request.php:683 mod/dfrn_request.php:700 +msgid "Confirm" +msgstr "Подтвердить" + +#: mod/dfrn_request.php:695 msgid "Hide this contact" msgstr "Скрыть этот контакт" -#: ../../mod/dfrn_request.php:674 +#: mod/dfrn_request.php:698 #, php-format msgid "Welcome home %s." msgstr "Добро пожаловать домой, %s!" -#: ../../mod/dfrn_request.php:675 +#: mod/dfrn_request.php:699 #, php-format msgid "Please confirm your introduction/connection request to %s." -msgstr "Пожалуйста, подтвердите краткую информацию / запрос на подключение к %s." +msgstr "" +"Пожалуйста, подтвердите краткую информацию / запрос на подключение к %s." -#: ../../mod/dfrn_request.php:676 -msgid "Confirm" -msgstr "Подтвердить" - -#: ../../mod/dfrn_request.php:804 +#: mod/dfrn_request.php:828 msgid "" "Please enter your 'Identity Address' from one of the following supported " "communications networks:" -msgstr "Пожалуйста, введите ваш 'идентификационный адрес' одной из следующих поддерживаемых социальных сетей:" +msgstr "" +"Пожалуйста, введите ваш 'идентификационный адрес' одной из следующих " +"поддерживаемых социальных сетей:" -#: ../../mod/dfrn_request.php:824 +#: mod/dfrn_request.php:849 +#, php-format msgid "" -"If you are not yet a member of the free social web, <a " -"href=\"http://dir.friendica.com/siteinfo\">follow this link to find a public" -" Friendica site and join us today</a>." -msgstr "Если вы еще не являетесь членом свободной социальной сети, перейдите по <a href=\"http://dir.friendica.com/siteinfo\"> этой ссылке, чтобы найти публичный сервер Friendica и присоединиться к нам сейчас </a>." +"If you are not yet a member of the free social web, <a href=\"%s/siteinfo" +"\">follow this link to find a public Friendica site and join us today</a>." +msgstr "" -#: ../../mod/dfrn_request.php:827 +#: mod/dfrn_request.php:854 msgid "Friend/Connection Request" msgstr "Запрос в друзья / на подключение" -#: ../../mod/dfrn_request.php:828 +#: mod/dfrn_request.php:855 msgid "" "Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, " "testuser@identi.ca" -msgstr "Примеры: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca" +msgstr "" +"Примеры: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, " +"testuser@identi.ca" -#: ../../mod/dfrn_request.php:829 -msgid "Please answer the following:" -msgstr "Пожалуйста, ответьте следующее:" - -#: ../../mod/dfrn_request.php:830 -#, php-format -msgid "Does %s know you?" -msgstr "%s знает вас?" - -#: ../../mod/dfrn_request.php:834 -msgid "Add a personal note:" -msgstr "Добавить личную заметку:" - -#: ../../mod/dfrn_request.php:836 ../../include/contact_selectors.php:76 +#: mod/dfrn_request.php:863 include/contact_selectors.php:76 msgid "Friendica" msgstr "Friendica" -#: ../../mod/dfrn_request.php:837 +#: mod/dfrn_request.php:864 msgid "StatusNet/Federated Social Web" msgstr "StatusNet / Federated Social Web" -#: ../../mod/dfrn_request.php:839 +#: mod/dfrn_request.php:866 #, php-format msgid "" -" - please do not use this form. Instead, enter %s into your Diaspora search" -" bar." -msgstr "Участники сети Diaspora: пожалуйста, не пользуйтесь этой формой. Вместо этого введите %s в строке поиска Diaspora" +" - please do not use this form. Instead, enter %s into your Diaspora search " +"bar." +msgstr "" +"Участники сети Diaspora: пожалуйста, не пользуйтесь этой формой. Вместо " +"этого введите %s в строке поиска Diaspora" -#: ../../mod/dfrn_request.php:840 -msgid "Your Identity Address:" -msgstr "Ваш идентификационный адрес:" - -#: ../../mod/dfrn_request.php:843 -msgid "Submit Request" -msgstr "Отправить запрос" - -#: ../../mod/register.php:90 +#: mod/register.php:92 msgid "" "Registration successful. Please check your email for further instructions." -msgstr "Регистрация успешна. Пожалуйста, проверьте свою электронную почту для получения дальнейших инструкций." +msgstr "" +"Регистрация успешна. Пожалуйста, проверьте свою электронную почту для " +"получения дальнейших инструкций." -#: ../../mod/register.php:96 +#: mod/register.php:97 #, php-format msgid "" "Failed to send email message. Here your accout details:<br> login: %s<br> " "password: %s<br><br>You can change your password after login." msgstr "" -#: ../../mod/register.php:105 +#: mod/register.php:104 +msgid "Registration successful." +msgstr "" + +#: mod/register.php:110 msgid "Your registration can not be processed." msgstr "Ваша регистрация не может быть обработана." -#: ../../mod/register.php:148 +#: mod/register.php:153 msgid "Your registration is pending approval by the site owner." msgstr "Ваша регистрация в ожидании одобрения владельцем сайта." -#: ../../mod/register.php:186 ../../mod/uimport.php:50 +#: mod/register.php:191 mod/uimport.php:50 msgid "" "This site has exceeded the number of allowed daily account registrations. " "Please try again tomorrow." -msgstr "Этот сайт превысил допустимое количество ежедневных регистраций. Пожалуйста, повторите попытку завтра." +msgstr "" +"Этот сайт превысил допустимое количество ежедневных регистраций. Пожалуйста, " +"повторите попытку завтра." -#: ../../mod/register.php:214 +#: mod/register.php:219 msgid "" "You may (optionally) fill in this form via OpenID by supplying your OpenID " "and clicking 'Register'." -msgstr "Вы можете (по желанию), заполнить эту форму с помощью OpenID, поддерживая ваш OpenID и нажав клавишу \"Регистрация\"." +msgstr "" +"Вы можете (по желанию), заполнить эту форму с помощью OpenID, поддерживая " +"ваш OpenID и нажав клавишу \"Регистрация\"." -#: ../../mod/register.php:215 +#: mod/register.php:220 msgid "" "If you are not familiar with OpenID, please leave that field blank and fill " "in the rest of the items." -msgstr "Если вы не знакомы с OpenID, пожалуйста, оставьте это поле пустым и заполните остальные элементы." +msgstr "" +"Если вы не знакомы с OpenID, пожалуйста, оставьте это поле пустым и " +"заполните остальные элементы." -#: ../../mod/register.php:216 +#: mod/register.php:221 msgid "Your OpenID (optional): " msgstr "Ваш OpenID (необязательно):" -#: ../../mod/register.php:230 +#: mod/register.php:235 msgid "Include your profile in member directory?" msgstr "Включить ваш профиль в каталог участников?" -#: ../../mod/register.php:251 +#: mod/register.php:259 msgid "Membership on this site is by invitation only." msgstr "Членство на сайте только по приглашению." -#: ../../mod/register.php:252 +#: mod/register.php:260 msgid "Your invitation ID: " msgstr "ID вашего приглашения:" -#: ../../mod/register.php:263 -msgid "Your Full Name (e.g. Joe Smith): " -msgstr "Ваше полное имя (например, Joe Smith): " +#: mod/register.php:271 +msgid "Your Full Name (e.g. Joe Smith, real or real-looking): " +msgstr "" -#: ../../mod/register.php:264 +#: mod/register.php:272 msgid "Your Email Address: " msgstr "Ваш адрес электронной почты: " -#: ../../mod/register.php:265 +#: mod/register.php:274 +msgid "Leave empty for an auto generated password." +msgstr "" + +#: mod/register.php:276 msgid "" "Choose a profile nickname. This must begin with a text character. Your " -"profile address on this site will then be " -"'<strong>nickname@$sitename</strong>'." -msgstr "Выбор псевдонима профиля. Он должен начинаться с буквы. Адрес вашего профиля на данном сайте будет в этом случае '<strong>nickname@$sitename</strong>'." +"profile address on this site will then be '<strong>nickname@$sitename</" +"strong>'." +msgstr "" +"Выбор псевдонима профиля. Он должен начинаться с буквы. Адрес вашего профиля " +"на данном сайте будет в этом случае '<strong>nickname@$sitename</strong>'." -#: ../../mod/register.php:266 +#: mod/register.php:277 msgid "Choose a nickname: " msgstr "Выберите псевдоним: " -#: ../../mod/register.php:269 ../../boot.php:1241 ../../include/nav.php:109 +#: mod/register.php:280 boot.php:1405 include/nav.php:108 msgid "Register" msgstr "Регистрация" -#: ../../mod/register.php:275 ../../mod/uimport.php:64 +#: mod/register.php:286 mod/uimport.php:64 msgid "Import" msgstr "Импорт" -#: ../../mod/register.php:276 +#: mod/register.php:287 msgid "Import your profile to this friendica instance" msgstr "Импорт своего профиля в этот экземпляр friendica" -#: ../../mod/maintenance.php:5 +#: mod/maintenance.php:5 msgid "System down for maintenance" msgstr "Система закрыта на техническое обслуживание" -#: ../../mod/search.php:99 ../../include/text.php:953 -#: ../../include/text.php:954 ../../include/nav.php:119 +#: mod/search.php:100 +msgid "Only logged in users are permitted to perform a search." +msgstr "" + +#: mod/search.php:124 +msgid "Too Many Requests" +msgstr "" + +#: mod/search.php:125 +msgid "Only one search per minute is permitted for not logged in users." +msgstr "" + +#: mod/search.php:136 include/text.php:1003 include/nav.php:118 msgid "Search" msgstr "Поиск" -#: ../../mod/directory.php:51 ../../view/theme/diabook/theme.php:525 -msgid "Global Directory" -msgstr "Глобальный каталог" +#: mod/search.php:234 +#, php-format +msgid "Items tagged with: %s" +msgstr "" -#: ../../mod/directory.php:59 -msgid "Find on this site" -msgstr "Найти на этом сайте" +#: mod/search.php:236 +#, php-format +msgid "Search results for: %s" +msgstr "" -#: ../../mod/directory.php:62 -msgid "Site Directory" -msgstr "Каталог сайта" - -#: ../../mod/directory.php:113 ../../mod/profiles.php:750 -msgid "Age: " -msgstr "Возраст: " - -#: ../../mod/directory.php:116 -msgid "Gender: " -msgstr "Пол: " - -#: ../../mod/directory.php:138 ../../boot.php:1650 -#: ../../include/profile_advanced.php:17 -msgid "Gender:" -msgstr "Пол:" - -#: ../../mod/directory.php:140 ../../boot.php:1653 -#: ../../include/profile_advanced.php:37 +#: mod/directory.php:149 include/identity.php:313 include/identity.php:610 msgid "Status:" msgstr "Статус:" -#: ../../mod/directory.php:142 ../../boot.php:1655 -#: ../../include/profile_advanced.php:48 +#: mod/directory.php:151 include/identity.php:315 include/identity.php:621 msgid "Homepage:" msgstr "Домашняя страничка:" -#: ../../mod/directory.php:144 ../../boot.php:1657 -#: ../../include/profile_advanced.php:58 -msgid "About:" -msgstr "О себе:" +#: mod/directory.php:203 view/theme/diabook/theme.php:525 +#: view/theme/vier/theme.php:205 +msgid "Global Directory" +msgstr "Глобальный каталог" -#: ../../mod/directory.php:189 +#: mod/directory.php:205 +msgid "Find on this site" +msgstr "Найти на этом сайте" + +#: mod/directory.php:207 +msgid "Finding:" +msgstr "" + +#: mod/directory.php:209 +msgid "Site Directory" +msgstr "Каталог сайта" + +#: mod/directory.php:216 msgid "No entries (some entries may be hidden)." msgstr "Нет записей (некоторые записи могут быть скрыты)." -#: ../../mod/delegate.php:101 +#: mod/delegate.php:101 msgid "No potential page delegates located." msgstr "" -#: ../../mod/delegate.php:130 ../../include/nav.php:170 +#: mod/delegate.php:130 include/nav.php:180 msgid "Delegate Page Management" msgstr "Делегировать управление страницей" -#: ../../mod/delegate.php:132 +#: mod/delegate.php:132 msgid "" "Delegates are able to manage all aspects of this account/page except for " "basic account settings. Please do not delegate your personal account to " "anybody that you do not trust completely." -msgstr "Доверенные лица могут управлять всеми аспектами этого аккаунта/страницы, за исключением основных настроек аккаунта. Пожалуйста, не предоставляйте доступ в личный кабинет тому, кому вы не полностью доверяете." +msgstr "" +"Доверенные лица могут управлять всеми аспектами этого аккаунта/страницы, за " +"исключением основных настроек аккаунта. Пожалуйста, не предоставляйте доступ " +"в личный кабинет тому, кому вы не полностью доверяете." -#: ../../mod/delegate.php:133 +#: mod/delegate.php:133 msgid "Existing Page Managers" msgstr "Существующие менеджеры страницы" -#: ../../mod/delegate.php:135 +#: mod/delegate.php:135 msgid "Existing Page Delegates" msgstr "Существующие уполномоченные страницы" -#: ../../mod/delegate.php:137 +#: mod/delegate.php:137 msgid "Potential Delegates" msgstr "Возможные доверенные лица" -#: ../../mod/delegate.php:140 +#: mod/delegate.php:140 msgid "Add" msgstr "Добавить" -#: ../../mod/delegate.php:141 +#: mod/delegate.php:141 msgid "No entries." msgstr "Нет записей." -#: ../../mod/common.php:42 -msgid "Common Friends" -msgstr "Общие друзья" - -#: ../../mod/common.php:78 +#: mod/common.php:86 msgid "No contacts in common." msgstr "Нет общих контактов." -#: ../../mod/uexport.php:77 +#: mod/uexport.php:77 msgid "Export account" msgstr "Экспорт аккаунта" -#: ../../mod/uexport.php:77 +#: mod/uexport.php:77 msgid "" "Export your account info and contacts. Use this to make a backup of your " "account and/or to move it to another server." -msgstr "Экспорт ваших регистрационных данные и контактов. Используйте, чтобы создать резервную копию вашего аккаунта и/или переместить его на другой сервер." +msgstr "" +"Экспорт ваших регистрационных данные и контактов. Используйте, чтобы создать " +"резервную копию вашего аккаунта и/или переместить его на другой сервер." -#: ../../mod/uexport.php:78 +#: mod/uexport.php:78 msgid "Export all" msgstr "Экспорт всего" -#: ../../mod/uexport.php:78 +#: mod/uexport.php:78 msgid "" "Export your accout info, contacts and all your items as json. Could be a " "very big file, and could take a lot of time. Use this to make a full backup " "of your account (photos are not exported)" -msgstr "Экспорт информации вашего аккаунта, контактов и всех ваших пунктов, как JSON. Может получиться очень большой файл и это может занять много времени. Используйте, чтобы создать полную резервную копию вашего аккаунта (фото не экспортируются)" +msgstr "" +"Экспорт информации вашего аккаунта, контактов и всех ваших пунктов, как " +"JSON. Может получиться очень большой файл и это может занять много времени. " +"Используйте, чтобы создать полную резервную копию вашего аккаунта (фото не " +"экспортируются)" -#: ../../mod/mood.php:62 ../../include/conversation.php:227 +#: mod/mood.php:62 include/conversation.php:239 #, php-format msgid "%1$s is currently %2$s" msgstr "" -#: ../../mod/mood.php:133 +#: mod/mood.php:133 msgid "Mood" msgstr "Настроение" -#: ../../mod/mood.php:134 +#: mod/mood.php:134 msgid "Set your current mood and tell your friends" msgstr "Напишите о вашем настроении и расскажите своим друзьям" -#: ../../mod/suggest.php:27 +#: mod/suggest.php:27 msgid "Do you really want to delete this suggestion?" msgstr "Вы действительно хотите удалить это предложение?" -#: ../../mod/suggest.php:68 ../../include/contact_widgets.php:35 -#: ../../view/theme/diabook/theme.php:527 -msgid "Friend Suggestions" -msgstr "Предложения друзей" - -#: ../../mod/suggest.php:74 +#: mod/suggest.php:71 msgid "" "No suggestions available. If this is a new site, please try again in 24 " "hours." -msgstr "Нет предложений. Если это новый сайт, пожалуйста, попробуйте снова через 24 часа." +msgstr "" +"Нет предложений. Если это новый сайт, пожалуйста, попробуйте снова через 24 " +"часа." -#: ../../mod/suggest.php:92 +#: mod/suggest.php:83 mod/suggest.php:101 msgid "Ignore/Hide" msgstr "Проигнорировать/Скрыть" -#: ../../mod/profiles.php:37 +#: mod/suggest.php:111 include/contact_widgets.php:35 +#: view/theme/diabook/theme.php:527 view/theme/vier/theme.php:207 +msgid "Friend Suggestions" +msgstr "Предложения друзей" + +#: mod/profiles.php:37 msgid "Profile deleted." msgstr "Профиль удален." -#: ../../mod/profiles.php:55 ../../mod/profiles.php:89 +#: mod/profiles.php:55 mod/profiles.php:89 msgid "Profile-" msgstr "Профиль-" -#: ../../mod/profiles.php:74 ../../mod/profiles.php:117 +#: mod/profiles.php:74 mod/profiles.php:117 msgid "New profile created." msgstr "Новый профиль создан." -#: ../../mod/profiles.php:95 +#: mod/profiles.php:95 msgid "Profile unavailable to clone." msgstr "Профиль недоступен для клонирования." -#: ../../mod/profiles.php:189 +#: mod/profiles.php:189 msgid "Profile Name is required." msgstr "Необходимо имя профиля." -#: ../../mod/profiles.php:340 +#: mod/profiles.php:336 msgid "Marital Status" msgstr "Семейное положение" -#: ../../mod/profiles.php:344 +#: mod/profiles.php:340 msgid "Romantic Partner" msgstr "Любимый человек" -#: ../../mod/profiles.php:348 +#: mod/profiles.php:344 mod/photos.php:1647 include/conversation.php:508 msgid "Likes" msgstr "Лайки" -#: ../../mod/profiles.php:352 +#: mod/profiles.php:348 mod/photos.php:1647 include/conversation.php:508 msgid "Dislikes" msgstr "Дизлайк" -#: ../../mod/profiles.php:356 +#: mod/profiles.php:352 msgid "Work/Employment" msgstr "Работа/Занятость" -#: ../../mod/profiles.php:359 +#: mod/profiles.php:355 msgid "Religion" msgstr "Религия" -#: ../../mod/profiles.php:363 +#: mod/profiles.php:359 msgid "Political Views" msgstr "Политические взгляды" -#: ../../mod/profiles.php:367 +#: mod/profiles.php:363 msgid "Gender" msgstr "Пол" -#: ../../mod/profiles.php:371 +#: mod/profiles.php:367 msgid "Sexual Preference" msgstr "Сексуальные предпочтения" -#: ../../mod/profiles.php:375 +#: mod/profiles.php:371 msgid "Homepage" msgstr "Домашняя страница" -#: ../../mod/profiles.php:379 ../../mod/profiles.php:698 +#: mod/profiles.php:375 mod/profiles.php:708 msgid "Interests" msgstr "Хобби / Интересы" -#: ../../mod/profiles.php:383 +#: mod/profiles.php:379 msgid "Address" msgstr "Адрес" -#: ../../mod/profiles.php:390 ../../mod/profiles.php:694 +#: mod/profiles.php:386 mod/profiles.php:704 msgid "Location" msgstr "Местонахождение" -#: ../../mod/profiles.php:473 +#: mod/profiles.php:469 msgid "Profile updated." msgstr "Профиль обновлен." -#: ../../mod/profiles.php:568 +#: mod/profiles.php:565 msgid " and " msgstr "и" -#: ../../mod/profiles.php:576 +#: mod/profiles.php:573 msgid "public profile" msgstr "публичный профиль" -#: ../../mod/profiles.php:579 +#: mod/profiles.php:576 #, php-format msgid "%1$s changed %2$s to “%3$s”" msgstr "%1$s изменились с %2$s на “%3$s”" -#: ../../mod/profiles.php:580 +#: mod/profiles.php:577 #, php-format msgid " - Visit %1$s's %2$s" msgstr " - Посетить профиль %1$s [%2$s]" -#: ../../mod/profiles.php:583 +#: mod/profiles.php:580 #, php-format msgid "%1$s has an updated %2$s, changing %3$s." msgstr "" -#: ../../mod/profiles.php:658 +#: mod/profiles.php:655 msgid "Hide contacts and friends:" msgstr "" -#: ../../mod/profiles.php:663 +#: mod/profiles.php:660 msgid "Hide your contact/friend list from viewers of this profile?" msgstr "Скрывать ваш список контактов / друзей от посетителей этого профиля?" -#: ../../mod/profiles.php:685 +#: mod/profiles.php:684 +msgid "Show more profile fields:" +msgstr "" + +#: mod/profiles.php:695 msgid "Edit Profile Details" msgstr "Редактировать детали профиля" -#: ../../mod/profiles.php:687 +#: mod/profiles.php:697 msgid "Change Profile Photo" msgstr "Изменить фото профиля" -#: ../../mod/profiles.php:688 +#: mod/profiles.php:698 msgid "View this profile" msgstr "Просмотреть этот профиль" -#: ../../mod/profiles.php:689 +#: mod/profiles.php:699 msgid "Create a new profile using these settings" msgstr "Создать новый профиль, используя эти настройки" -#: ../../mod/profiles.php:690 +#: mod/profiles.php:700 msgid "Clone this profile" msgstr "Клонировать этот профиль" -#: ../../mod/profiles.php:691 +#: mod/profiles.php:701 msgid "Delete this profile" msgstr "Удалить этот профиль" -#: ../../mod/profiles.php:692 +#: mod/profiles.php:702 msgid "Basic information" msgstr "" -#: ../../mod/profiles.php:693 +#: mod/profiles.php:703 msgid "Profile picture" msgstr "" -#: ../../mod/profiles.php:695 +#: mod/profiles.php:705 msgid "Preferences" msgstr "" -#: ../../mod/profiles.php:696 +#: mod/profiles.php:706 msgid "Status information" msgstr "" -#: ../../mod/profiles.php:697 +#: mod/profiles.php:707 msgid "Additional information" msgstr "" -#: ../../mod/profiles.php:700 +#: mod/profiles.php:710 msgid "Profile Name:" msgstr "Имя профиля:" -#: ../../mod/profiles.php:701 +#: mod/profiles.php:711 msgid "Your Full Name:" msgstr "Ваше полное имя:" -#: ../../mod/profiles.php:702 +#: mod/profiles.php:712 msgid "Title/Description:" msgstr "Заголовок / Описание:" -#: ../../mod/profiles.php:703 +#: mod/profiles.php:713 msgid "Your Gender:" msgstr "Ваш пол:" -#: ../../mod/profiles.php:704 -#, php-format -msgid "Birthday (%s):" -msgstr "День рождения (%s):" +#: mod/profiles.php:714 +msgid "Birthday :" +msgstr "" -#: ../../mod/profiles.php:705 +#: mod/profiles.php:715 msgid "Street Address:" msgstr "Адрес:" -#: ../../mod/profiles.php:706 +#: mod/profiles.php:716 msgid "Locality/City:" msgstr "Город / Населенный пункт:" -#: ../../mod/profiles.php:707 +#: mod/profiles.php:717 msgid "Postal/Zip Code:" msgstr "Почтовый индекс:" -#: ../../mod/profiles.php:708 +#: mod/profiles.php:718 msgid "Country:" msgstr "Страна:" -#: ../../mod/profiles.php:709 +#: mod/profiles.php:719 msgid "Region/State:" msgstr "Район / Область:" -#: ../../mod/profiles.php:710 +#: mod/profiles.php:720 msgid "<span class=\"heart\">♥</span> Marital Status:" msgstr "<span class=\"heart\">♥</span> Семейное положение:" -#: ../../mod/profiles.php:711 +#: mod/profiles.php:721 msgid "Who: (if applicable)" msgstr "Кто: (если требуется)" -#: ../../mod/profiles.php:712 +#: mod/profiles.php:722 msgid "Examples: cathy123, Cathy Williams, cathy@example.com" msgstr "Примеры: cathy123, Кэти Уильямс, cathy@example.com" -#: ../../mod/profiles.php:713 +#: mod/profiles.php:723 msgid "Since [date]:" msgstr "С какого времени [дата]:" -#: ../../mod/profiles.php:714 ../../include/profile_advanced.php:46 +#: mod/profiles.php:724 include/identity.php:619 msgid "Sexual Preference:" msgstr "Сексуальные предпочтения:" -#: ../../mod/profiles.php:715 +#: mod/profiles.php:725 msgid "Homepage URL:" msgstr "Адрес домашней странички:" -#: ../../mod/profiles.php:716 ../../include/profile_advanced.php:50 +#: mod/profiles.php:726 include/identity.php:623 msgid "Hometown:" msgstr "Родной город:" -#: ../../mod/profiles.php:717 ../../include/profile_advanced.php:54 +#: mod/profiles.php:727 include/identity.php:627 msgid "Political Views:" msgstr "Политические взгляды:" -#: ../../mod/profiles.php:718 +#: mod/profiles.php:728 msgid "Religious Views:" msgstr "Религиозные взгляды:" -#: ../../mod/profiles.php:719 +#: mod/profiles.php:729 msgid "Public Keywords:" msgstr "Общественные ключевые слова:" -#: ../../mod/profiles.php:720 +#: mod/profiles.php:730 msgid "Private Keywords:" msgstr "Личные ключевые слова:" -#: ../../mod/profiles.php:721 ../../include/profile_advanced.php:62 +#: mod/profiles.php:731 include/identity.php:635 msgid "Likes:" msgstr "Нравится:" -#: ../../mod/profiles.php:722 ../../include/profile_advanced.php:64 +#: mod/profiles.php:732 include/identity.php:637 msgid "Dislikes:" msgstr "Не нравится:" -#: ../../mod/profiles.php:723 +#: mod/profiles.php:733 msgid "Example: fishing photography software" msgstr "Пример: рыбалка фотографии программное обеспечение" -#: ../../mod/profiles.php:724 +#: mod/profiles.php:734 msgid "(Used for suggesting potential friends, can be seen by others)" -msgstr "(Используется для предложения потенциальным друзьям, могут увидеть другие)" +msgstr "" +"(Используется для предложения потенциальным друзьям, могут увидеть другие)" -#: ../../mod/profiles.php:725 +#: mod/profiles.php:735 msgid "(Used for searching profiles, never shown to others)" msgstr "(Используется для поиска профилей, никогда не показывается другим)" -#: ../../mod/profiles.php:726 +#: mod/profiles.php:736 msgid "Tell us about yourself..." msgstr "Расскажите нам о себе ..." -#: ../../mod/profiles.php:727 +#: mod/profiles.php:737 msgid "Hobbies/Interests" msgstr "Хобби / Интересы" -#: ../../mod/profiles.php:728 +#: mod/profiles.php:738 msgid "Contact information and Social Networks" msgstr "Контактная информация и социальные сети" -#: ../../mod/profiles.php:729 +#: mod/profiles.php:739 msgid "Musical interests" msgstr "Музыкальные интересы" -#: ../../mod/profiles.php:730 +#: mod/profiles.php:740 msgid "Books, literature" msgstr "Книги, литература" -#: ../../mod/profiles.php:731 +#: mod/profiles.php:741 msgid "Television" msgstr "Телевидение" -#: ../../mod/profiles.php:732 +#: mod/profiles.php:742 msgid "Film/dance/culture/entertainment" msgstr "Кино / танцы / культура / развлечения" -#: ../../mod/profiles.php:733 +#: mod/profiles.php:743 msgid "Love/romance" msgstr "Любовь / романтика" -#: ../../mod/profiles.php:734 +#: mod/profiles.php:744 msgid "Work/employment" msgstr "Работа / занятость" -#: ../../mod/profiles.php:735 +#: mod/profiles.php:745 msgid "School/education" msgstr "Школа / образование" -#: ../../mod/profiles.php:740 +#: mod/profiles.php:750 msgid "" "This is your <strong>public</strong> profile.<br />It <strong>may</strong> " "be visible to anybody using the internet." -msgstr "Это ваш <strong>публичный</strong> профиль. <br /> Он <strong>может</strong> быть виден каждому через Интернет." +msgstr "" +"Это ваш <strong>публичный</strong> профиль. <br /> Он <strong>может</strong> " +"быть виден каждому через Интернет." -#: ../../mod/profiles.php:803 +#: mod/profiles.php:760 +msgid "Age: " +msgstr "Возраст: " + +#: mod/profiles.php:813 msgid "Edit/Manage Profiles" msgstr "Редактировать профиль" -#: ../../mod/profiles.php:804 ../../boot.php:1611 ../../boot.php:1637 +#: mod/profiles.php:814 include/identity.php:260 include/identity.php:286 msgid "Change profile photo" msgstr "Изменить фото профиля" -#: ../../mod/profiles.php:805 ../../boot.php:1612 +#: mod/profiles.php:815 include/identity.php:261 msgid "Create New Profile" msgstr "Создать новый профиль" -#: ../../mod/profiles.php:816 ../../boot.php:1622 +#: mod/profiles.php:826 include/identity.php:271 msgid "Profile Image" msgstr "Фото профиля" -#: ../../mod/profiles.php:818 ../../boot.php:1625 +#: mod/profiles.php:828 include/identity.php:274 msgid "visible to everybody" msgstr "видимый всем" -#: ../../mod/profiles.php:819 ../../boot.php:1626 +#: mod/profiles.php:829 include/identity.php:275 msgid "Edit visibility" msgstr "Редактировать видимость" -#: ../../mod/editpost.php:17 ../../mod/editpost.php:27 +#: mod/editpost.php:17 mod/editpost.php:27 msgid "Item not found" msgstr "Элемент не найден" -#: ../../mod/editpost.php:39 +#: mod/editpost.php:40 msgid "Edit post" msgstr "Редактировать сообщение" -#: ../../mod/editpost.php:111 ../../include/conversation.php:1092 +#: mod/editpost.php:111 include/conversation.php:1184 msgid "upload photo" msgstr "загрузить фото" -#: ../../mod/editpost.php:112 ../../include/conversation.php:1093 +#: mod/editpost.php:112 include/conversation.php:1185 msgid "Attach file" -msgstr "Приложить файл" +msgstr "Прикрепить файл" -#: ../../mod/editpost.php:113 ../../include/conversation.php:1094 +#: mod/editpost.php:113 include/conversation.php:1186 msgid "attach file" msgstr "приложить файл" -#: ../../mod/editpost.php:115 ../../include/conversation.php:1096 +#: mod/editpost.php:115 include/conversation.php:1188 msgid "web link" msgstr "веб-ссылка" -#: ../../mod/editpost.php:116 ../../include/conversation.php:1097 +#: mod/editpost.php:116 include/conversation.php:1189 msgid "Insert video link" msgstr "Вставить ссылку видео" -#: ../../mod/editpost.php:117 ../../include/conversation.php:1098 +#: mod/editpost.php:117 include/conversation.php:1190 msgid "video link" msgstr "видео-ссылка" -#: ../../mod/editpost.php:118 ../../include/conversation.php:1099 +#: mod/editpost.php:118 include/conversation.php:1191 msgid "Insert audio link" msgstr "Вставить ссылку аудио" -#: ../../mod/editpost.php:119 ../../include/conversation.php:1100 +#: mod/editpost.php:119 include/conversation.php:1192 msgid "audio link" msgstr "аудио-ссылка" -#: ../../mod/editpost.php:120 ../../include/conversation.php:1101 +#: mod/editpost.php:120 include/conversation.php:1193 msgid "Set your location" msgstr "Задать ваше местоположение" -#: ../../mod/editpost.php:121 ../../include/conversation.php:1102 +#: mod/editpost.php:121 include/conversation.php:1194 msgid "set location" msgstr "установить местонахождение" -#: ../../mod/editpost.php:122 ../../include/conversation.php:1103 +#: mod/editpost.php:122 include/conversation.php:1195 msgid "Clear browser location" msgstr "Очистить местонахождение браузера" -#: ../../mod/editpost.php:123 ../../include/conversation.php:1104 +#: mod/editpost.php:123 include/conversation.php:1196 msgid "clear location" msgstr "убрать местонахождение" -#: ../../mod/editpost.php:125 ../../include/conversation.php:1110 +#: mod/editpost.php:125 include/conversation.php:1202 msgid "Permission settings" msgstr "Настройки разрешений" -#: ../../mod/editpost.php:133 ../../include/conversation.php:1119 +#: mod/editpost.php:133 include/acl_selectors.php:344 msgid "CC: email addresses" msgstr "Копии на email адреса" -#: ../../mod/editpost.php:134 ../../include/conversation.php:1120 +#: mod/editpost.php:134 include/conversation.php:1211 msgid "Public post" msgstr "Публичное сообщение" -#: ../../mod/editpost.php:137 ../../include/conversation.php:1106 +#: mod/editpost.php:137 include/conversation.php:1198 msgid "Set title" msgstr "Установить заголовок" -#: ../../mod/editpost.php:139 ../../include/conversation.php:1108 +#: mod/editpost.php:139 include/conversation.php:1200 msgid "Categories (comma-separated list)" msgstr "Категории (список через запятую)" -#: ../../mod/editpost.php:140 ../../include/conversation.php:1122 +#: mod/editpost.php:140 include/acl_selectors.php:345 msgid "Example: bob@example.com, mary@example.com" msgstr "Пример: bob@example.com, mary@example.com" -#: ../../mod/friendica.php:59 +#: mod/friendica.php:70 msgid "This is Friendica, version" msgstr "Это Friendica, версия" -#: ../../mod/friendica.php:60 +#: mod/friendica.php:71 msgid "running at web location" msgstr "работает на веб-узле" -#: ../../mod/friendica.php:62 +#: mod/friendica.php:73 msgid "" "Please visit <a href=\"http://friendica.com\">Friendica.com</a> to learn " "more about the Friendica project." -msgstr "Пожалуйста, посетите сайт <a href=\"http://friendica.com\">Friendica.com</a>, чтобы узнать больше о проекте Friendica." +msgstr "" +"Пожалуйста, посетите сайт <a href=\"http://friendica.com\">Friendica.com</" +"a>, чтобы узнать больше о проекте Friendica." -#: ../../mod/friendica.php:64 +#: mod/friendica.php:75 msgid "Bug reports and issues: please visit" msgstr "Отчет об ошибках и проблемах: пожалуйста, посетите" -#: ../../mod/friendica.php:65 +#: mod/friendica.php:75 +msgid "the bugtracker at github" +msgstr "" + +#: mod/friendica.php:76 msgid "" "Suggestions, praise, donations, etc. - please email \"Info\" at Friendica - " "dot com" -msgstr "Предложения, похвала, пожертвования? Пишите на \"info\" на Friendica - точка com" +msgstr "" +"Предложения, похвала, пожертвования? Пишите на \"info\" на Friendica - точка " +"com" -#: ../../mod/friendica.php:79 +#: mod/friendica.php:90 msgid "Installed plugins/addons/apps:" msgstr "Установленные плагины / добавки / приложения:" -#: ../../mod/friendica.php:92 +#: mod/friendica.php:103 msgid "No installed plugins/addons/apps" msgstr "Нет установленных плагинов / добавок / приложений" -#: ../../mod/api.php:76 ../../mod/api.php:102 +#: mod/api.php:76 mod/api.php:102 msgid "Authorize application connection" msgstr "Разрешить связь с приложением" -#: ../../mod/api.php:77 +#: mod/api.php:77 msgid "Return to your app and insert this Securty Code:" msgstr "Вернитесь в ваше приложение и задайте этот код:" -#: ../../mod/api.php:89 +#: mod/api.php:89 msgid "Please login to continue." msgstr "Пожалуйста, войдите для продолжения." -#: ../../mod/api.php:104 +#: mod/api.php:104 msgid "" -"Do you want to authorize this application to access your posts and contacts," -" and/or create new posts for you?" -msgstr "Вы действительно хотите разрешить этому приложению доступ к своим постам и контактам, а также создавать новые записи от вашего имени?" +"Do you want to authorize this application to access your posts and contacts, " +"and/or create new posts for you?" +msgstr "" +"Вы действительно хотите разрешить этому приложению доступ к своим постам и " +"контактам, а также создавать новые записи от вашего имени?" -#: ../../mod/lockview.php:31 ../../mod/lockview.php:39 +#: mod/lockview.php:31 mod/lockview.php:39 msgid "Remote privacy information not available." msgstr "Личная информация удаленно недоступна." -#: ../../mod/lockview.php:48 +#: mod/lockview.php:48 msgid "Visible to:" msgstr "Кто может видеть:" -#: ../../mod/notes.php:44 ../../boot.php:2150 +#: mod/notes.php:46 include/identity.php:730 msgid "Personal Notes" msgstr "Личные заметки" -#: ../../mod/localtime.php:12 ../../include/bb2diaspora.php:148 -#: ../../include/event.php:11 +#: mod/localtime.php:12 include/bb2diaspora.php:148 include/event.php:13 msgid "l F d, Y \\@ g:i A" msgstr "l F d, Y \\@ g:i A" -#: ../../mod/localtime.php:24 +#: mod/localtime.php:24 msgid "Time Conversion" msgstr "История общения" -#: ../../mod/localtime.php:26 +#: mod/localtime.php:26 msgid "" "Friendica provides this service for sharing events with other networks and " "friends in unknown timezones." -msgstr "Friendica предоставляет этот сервис для обмена событиями с другими сетями и друзьями, находящимися в неопределённых часовых поясах." +msgstr "" +"Friendica предоставляет этот сервис для обмена событиями с другими сетями и " +"друзьями, находящимися в неопределённых часовых поясах." -#: ../../mod/localtime.php:30 +#: mod/localtime.php:30 #, php-format msgid "UTC time: %s" msgstr "UTC время: %s" -#: ../../mod/localtime.php:33 +#: mod/localtime.php:33 #, php-format msgid "Current timezone: %s" msgstr "Ваш часовой пояс: %s" -#: ../../mod/localtime.php:36 +#: mod/localtime.php:36 #, php-format msgid "Converted localtime: %s" msgstr "Ваше изменённое время: %s" -#: ../../mod/localtime.php:41 +#: mod/localtime.php:41 msgid "Please select your timezone:" msgstr "Выберите пожалуйста ваш часовой пояс:" -#: ../../mod/poke.php:192 +#: mod/poke.php:191 msgid "Poke/Prod" msgstr "" -#: ../../mod/poke.php:193 +#: mod/poke.php:192 msgid "poke, prod or do other things to somebody" msgstr "" -#: ../../mod/poke.php:194 +#: mod/poke.php:193 msgid "Recipient" msgstr "Получатель" -#: ../../mod/poke.php:195 +#: mod/poke.php:194 msgid "Choose what you wish to do to recipient" msgstr "Выберите действия для получателя" -#: ../../mod/poke.php:198 +#: mod/poke.php:197 msgid "Make this post private" msgstr "Сделать эту запись личной" -#: ../../mod/invite.php:27 +#: mod/repair_ostatus.php:14 +msgid "Resubscribing to OStatus contacts" +msgstr "" + +#: mod/repair_ostatus.php:30 +msgid "Error" +msgstr "Ошибка" + +#: mod/invite.php:27 msgid "Total invitation limit exceeded." msgstr "Превышен общий лимит приглашений." -#: ../../mod/invite.php:49 +#: mod/invite.php:49 #, php-format msgid "%s : Not a valid email address." msgstr "%s: Неверный адрес электронной почты." -#: ../../mod/invite.php:73 +#: mod/invite.php:73 msgid "Please join us on Friendica" msgstr "Пожалуйста, присоединяйтесь к нам на Friendica" -#: ../../mod/invite.php:84 +#: mod/invite.php:84 msgid "Invitation limit exceeded. Please contact your site administrator." -msgstr "Лимит приглашений превышен. Пожалуйста, свяжитесь с администратором сайта." +msgstr "" +"Лимит приглашений превышен. Пожалуйста, свяжитесь с администратором сайта." -#: ../../mod/invite.php:89 +#: mod/invite.php:89 #, php-format msgid "%s : Message delivery failed." msgstr "%s: Доставка сообщения не удалась." -#: ../../mod/invite.php:93 +#: mod/invite.php:93 #, php-format msgid "%d message sent." msgid_plural "%d messages sent." msgstr[0] "%d сообщение отправлено." msgstr[1] "%d сообщений отправлено." msgstr[2] "%d сообщений отправлено." +msgstr[3] "%d сообщений отправлено." -#: ../../mod/invite.php:112 +#: mod/invite.php:112 msgid "You have no more invitations available" msgstr "У вас нет больше приглашений" -#: ../../mod/invite.php:120 +#: mod/invite.php:120 #, php-format msgid "" "Visit %s for a list of public sites that you can join. Friendica members on " -"other sites can all connect with each other, as well as with members of many" -" other social networks." -msgstr "Посетите %s со списком общедоступных сайтов, к которым вы можете присоединиться. Все участники Friendica на других сайтах могут соединиться друг с другом, а также с участниками многих других социальных сетей." +"other sites can all connect with each other, as well as with members of many " +"other social networks." +msgstr "" +"Посетите %s со списком общедоступных сайтов, к которым вы можете " +"присоединиться. Все участники Friendica на других сайтах могут соединиться " +"друг с другом, а также с участниками многих других социальных сетей." -#: ../../mod/invite.php:122 +#: mod/invite.php:122 #, php-format msgid "" "To accept this invitation, please visit and register at %s or any other " "public Friendica website." -msgstr "Для одобрения этого приглашения, пожалуйста, посетите и зарегистрируйтесь на %s ,или любом другом публичном сервере Friendica" +msgstr "" +"Для одобрения этого приглашения, пожалуйста, посетите и зарегистрируйтесь на " +"%s ,или любом другом публичном сервере Friendica" -#: ../../mod/invite.php:123 +#: mod/invite.php:123 #, php-format msgid "" "Friendica sites all inter-connect to create a huge privacy-enhanced social " "web that is owned and controlled by its members. They can also connect with " "many traditional social networks. See %s for a list of alternate Friendica " "sites you can join." -msgstr "Сайты Friendica, подключившись между собой, могут создать сеть с повышенной безопасностью, которая принадлежит и управляется её членами. Они также могут подключаться ко многим традиционным социальным сетям. См. %s со списком альтернативных сайтов Friendica, к которым вы можете присоединиться." +msgstr "" +"Сайты Friendica, подключившись между собой, могут создать сеть с повышенной " +"безопасностью, которая принадлежит и управляется её членами. Они также могут " +"подключаться ко многим традиционным социальным сетям. См. %s со списком " +"альтернативных сайтов Friendica, к которым вы можете присоединиться." -#: ../../mod/invite.php:126 +#: mod/invite.php:126 msgid "" -"Our apologies. This system is not currently configured to connect with other" -" public sites or invite members." -msgstr "Извините. Эта система в настоящее время не сконфигурирована для соединения с другими общественными сайтами и для приглашения участников." +"Our apologies. This system is not currently configured to connect with other " +"public sites or invite members." +msgstr "" +"Извините. Эта система в настоящее время не сконфигурирована для соединения с " +"другими общественными сайтами и для приглашения участников." -#: ../../mod/invite.php:132 +#: mod/invite.php:132 msgid "Send invitations" msgstr "Отправить приглашения" -#: ../../mod/invite.php:133 +#: mod/invite.php:133 msgid "Enter email addresses, one per line:" msgstr "Введите адреса электронной почты, по одному в строке:" -#: ../../mod/invite.php:135 +#: mod/invite.php:135 msgid "" "You are cordially invited to join me and other close friends on Friendica - " "and help us to create a better social web." -msgstr "Приглашаем Вас присоединиться ко мне и другим близким друзьям на Friendica - помочь нам создать лучшую социальную сеть." +msgstr "" +"Приглашаем Вас присоединиться ко мне и другим близким друзьям на Friendica - " +"помочь нам создать лучшую социальную сеть." -#: ../../mod/invite.php:137 +#: mod/invite.php:137 msgid "You will need to supply this invitation code: $invite_code" msgstr "Вам нужно будет предоставить этот код приглашения: $invite_code" -#: ../../mod/invite.php:137 +#: mod/invite.php:137 msgid "" "Once you have registered, please connect with me via my profile page at:" -msgstr "После того как вы зарегистрировались, пожалуйста, свяжитесь со мной через мою страницу профиля по адресу:" +msgstr "" +"После того как вы зарегистрировались, пожалуйста, свяжитесь со мной через " +"мою страницу профиля по адресу:" -#: ../../mod/invite.php:139 +#: mod/invite.php:139 msgid "" "For more information about the Friendica project and why we feel it is " "important, please visit http://friendica.com" -msgstr "Для получения более подробной информации о проекте Friendica, пожалуйста, посетите http://friendica.com" +msgstr "" +"Для получения более подробной информации о проекте Friendica, пожалуйста, " +"посетите http://friendica.com" -#: ../../mod/photos.php:52 ../../boot.php:2129 +#: mod/photos.php:99 include/identity.php:705 msgid "Photo Albums" msgstr "Фотоальбомы" -#: ../../mod/photos.php:60 ../../mod/photos.php:155 ../../mod/photos.php:1064 -#: ../../mod/photos.php:1187 ../../mod/photos.php:1210 -#: ../../mod/photos.php:1760 ../../mod/photos.php:1772 -#: ../../view/theme/diabook/theme.php:499 -msgid "Contact Photos" -msgstr "Фотографии контакта" +#: mod/photos.php:100 mod/photos.php:1899 +msgid "Recent Photos" +msgstr "Последние фото" -#: ../../mod/photos.php:67 ../../mod/photos.php:1262 ../../mod/photos.php:1819 +#: mod/photos.php:103 mod/photos.php:1320 mod/photos.php:1901 msgid "Upload New Photos" msgstr "Загрузить новые фото" -#: ../../mod/photos.php:144 +#: mod/photos.php:181 msgid "Contact information unavailable" msgstr "Информация о контакте недоступна" -#: ../../mod/photos.php:165 +#: mod/photos.php:202 msgid "Album not found." msgstr "Альбом не найден." -#: ../../mod/photos.php:188 ../../mod/photos.php:200 ../../mod/photos.php:1204 +#: mod/photos.php:232 mod/photos.php:244 mod/photos.php:1262 msgid "Delete Album" msgstr "Удалить альбом" -#: ../../mod/photos.php:198 +#: mod/photos.php:242 msgid "Do you really want to delete this photo album and all its photos?" msgstr "Вы действительно хотите удалить этот альбом и все его фотографии?" -#: ../../mod/photos.php:278 ../../mod/photos.php:289 ../../mod/photos.php:1515 +#: mod/photos.php:322 mod/photos.php:333 mod/photos.php:1580 msgid "Delete Photo" msgstr "Удалить фото" -#: ../../mod/photos.php:287 +#: mod/photos.php:331 msgid "Do you really want to delete this photo?" msgstr "Вы действительно хотите удалить эту фотографию?" -#: ../../mod/photos.php:662 +#: mod/photos.php:706 #, php-format msgid "%1$s was tagged in %2$s by %3$s" msgstr "%1$s отмечен/а/ в %2$s by %3$s" -#: ../../mod/photos.php:662 +#: mod/photos.php:706 msgid "a photo" msgstr "фото" -#: ../../mod/photos.php:767 -msgid "Image exceeds size limit of " -msgstr "Размер фото превышает лимит " - -#: ../../mod/photos.php:775 +#: mod/photos.php:819 msgid "Image file is empty." msgstr "Файл изображения пуст." -#: ../../mod/photos.php:930 +#: mod/photos.php:986 msgid "No photos selected" msgstr "Не выбрано фото." -#: ../../mod/photos.php:1094 +#: mod/photos.php:1147 #, php-format msgid "You have used %1$.2f Mbytes of %2$.2f Mbytes photo storage." -msgstr "Вы использовали %1$.2f мегабайт из %2$.2f возможных для хранения фотографий." +msgstr "" +"Вы использовали %1$.2f мегабайт из %2$.2f возможных для хранения фотографий." -#: ../../mod/photos.php:1129 +#: mod/photos.php:1182 msgid "Upload Photos" msgstr "Загрузить фото" -#: ../../mod/photos.php:1133 ../../mod/photos.php:1199 +#: mod/photos.php:1186 mod/photos.php:1257 msgid "New album name: " msgstr "Название нового альбома: " -#: ../../mod/photos.php:1134 +#: mod/photos.php:1187 msgid "or existing album name: " msgstr "или название существующего альбома: " -#: ../../mod/photos.php:1135 +#: mod/photos.php:1188 msgid "Do not show a status post for this upload" msgstr "Не показывать статус-сообщение для этой закачки" -#: ../../mod/photos.php:1137 ../../mod/photos.php:1510 +#: mod/photos.php:1190 mod/photos.php:1575 include/acl_selectors.php:347 msgid "Permissions" msgstr "Разрешения" -#: ../../mod/photos.php:1148 +#: mod/photos.php:1201 msgid "Private Photo" msgstr "Личное фото" -#: ../../mod/photos.php:1149 +#: mod/photos.php:1202 msgid "Public Photo" msgstr "Публичное фото" -#: ../../mod/photos.php:1212 +#: mod/photos.php:1270 msgid "Edit Album" msgstr "Редактировать альбом" -#: ../../mod/photos.php:1218 +#: mod/photos.php:1276 msgid "Show Newest First" msgstr "Показать новые первыми" -#: ../../mod/photos.php:1220 +#: mod/photos.php:1278 msgid "Show Oldest First" msgstr "Показать старые первыми" -#: ../../mod/photos.php:1248 ../../mod/photos.php:1802 +#: mod/photos.php:1306 mod/photos.php:1884 msgid "View Photo" msgstr "Просмотр фото" -#: ../../mod/photos.php:1294 +#: mod/photos.php:1353 msgid "Permission denied. Access to this item may be restricted." msgstr "Нет разрешения. Доступ к этому элементу ограничен." -#: ../../mod/photos.php:1296 +#: mod/photos.php:1355 msgid "Photo not available" msgstr "Фото недоступно" -#: ../../mod/photos.php:1352 +#: mod/photos.php:1411 msgid "View photo" msgstr "Просмотр фото" -#: ../../mod/photos.php:1352 +#: mod/photos.php:1411 msgid "Edit photo" msgstr "Редактировать фото" -#: ../../mod/photos.php:1353 +#: mod/photos.php:1412 msgid "Use as profile photo" msgstr "Использовать как фото профиля" -#: ../../mod/photos.php:1378 +#: mod/photos.php:1437 msgid "View Full Size" msgstr "Просмотреть полный размер" -#: ../../mod/photos.php:1457 +#: mod/photos.php:1523 msgid "Tags: " msgstr "Ключевые слова: " -#: ../../mod/photos.php:1460 +#: mod/photos.php:1526 msgid "[Remove any tag]" msgstr "[Удалить любое ключевое слово]" -#: ../../mod/photos.php:1500 -msgid "Rotate CW (right)" -msgstr "Поворот по часовой стрелке (направо)" - -#: ../../mod/photos.php:1501 -msgid "Rotate CCW (left)" -msgstr "Поворот против часовой стрелки (налево)" - -#: ../../mod/photos.php:1503 +#: mod/photos.php:1566 msgid "New album name" msgstr "Название нового альбома" -#: ../../mod/photos.php:1506 +#: mod/photos.php:1567 msgid "Caption" msgstr "Подпись" -#: ../../mod/photos.php:1508 +#: mod/photos.php:1568 msgid "Add a Tag" msgstr "Добавить ключевое слово (таг)" -#: ../../mod/photos.php:1512 -msgid "" -"Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping" +#: mod/photos.php:1568 +msgid "Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping" msgstr "Пример: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping" -#: ../../mod/photos.php:1521 +#: mod/photos.php:1569 +msgid "Do not rotate" +msgstr "" + +#: mod/photos.php:1570 +msgid "Rotate CW (right)" +msgstr "Поворот по часовой стрелке (направо)" + +#: mod/photos.php:1571 +msgid "Rotate CCW (left)" +msgstr "Поворот против часовой стрелки (налево)" + +#: mod/photos.php:1586 msgid "Private photo" msgstr "Личное фото" -#: ../../mod/photos.php:1522 +#: mod/photos.php:1587 msgid "Public photo" msgstr "Публичное фото" -#: ../../mod/photos.php:1544 ../../include/conversation.php:1090 +#: mod/photos.php:1609 include/conversation.php:1182 msgid "Share" msgstr "Поделиться" -#: ../../mod/photos.php:1817 -msgid "Recent Photos" -msgstr "Последние фото" +#: mod/photos.php:1648 include/conversation.php:509 +#: include/conversation.php:1413 +msgid "Attending" +msgid_plural "Attending" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" -#: ../../mod/regmod.php:55 +#: mod/photos.php:1648 include/conversation.php:509 +msgid "Not attending" +msgstr "" + +#: mod/photos.php:1648 include/conversation.php:509 +msgid "Might attend" +msgstr "" + +#: mod/photos.php:1813 +msgid "Map" +msgstr "Карта" + +#: mod/p.php:9 +msgid "Not Extended" +msgstr "" + +#: mod/regmod.php:55 msgid "Account approved." msgstr "Аккаунт утвержден." -#: ../../mod/regmod.php:92 +#: mod/regmod.php:92 #, php-format msgid "Registration revoked for %s" msgstr "Регистрация отменена для %s" -#: ../../mod/regmod.php:104 +#: mod/regmod.php:104 msgid "Please login." msgstr "Пожалуйста, войдите с паролем." -#: ../../mod/uimport.php:66 +#: mod/uimport.php:66 msgid "Move account" msgstr "Удалить аккаунт" -#: ../../mod/uimport.php:67 +#: mod/uimport.php:67 msgid "You can import an account from another Friendica server." msgstr "Вы можете импортировать учетную запись с другого сервера Friendica." -#: ../../mod/uimport.php:68 +#: mod/uimport.php:68 msgid "" "You need to export your account from the old server and upload it here. We " -"will recreate your old account here with all your contacts. We will try also" -" to inform your friends that you moved here." -msgstr "Вам нужно экспортировать свой аккаунт со старого сервера и загрузить его сюда. Мы восстановим ваш старый аккаунт здесь со всеми вашими контактами. Мы постараемся также сообщить друзьям, что вы переехали сюда." +"will recreate your old account here with all your contacts. We will try also " +"to inform your friends that you moved here." +msgstr "" +"Вам нужно экспортировать свой аккаунт со старого сервера и загрузить его " +"сюда. Мы восстановим ваш старый аккаунт здесь со всеми вашими контактами. Мы " +"постараемся также сообщить друзьям, что вы переехали сюда." -#: ../../mod/uimport.php:69 +#: mod/uimport.php:69 msgid "" "This feature is experimental. We can't import contacts from the OStatus " -"network (statusnet/identi.ca) or from Diaspora" -msgstr "Это экспериментальная функция. Мы не можем импортировать контакты из сети OStatus (StatusNet / identi.ca) или из Diaspora" +"network (GNU Social/Statusnet) or from Diaspora" +msgstr "" -#: ../../mod/uimport.php:70 +#: mod/uimport.php:70 msgid "Account file" msgstr "Файл аккаунта" -#: ../../mod/uimport.php:70 +#: mod/uimport.php:70 msgid "" "To export your account, go to \"Settings->Export your personal data\" and " "select \"Export account\"" -msgstr "Для экспорта аккаунта, перейдите в \"Настройки->Экспортировать ваши данные\" и выберите \"Экспорт аккаунта\"" +msgstr "" +"Для экспорта аккаунта, перейдите в \"Настройки->Экспортировать ваши данные\" " +"и выберите \"Экспорт аккаунта\"" -#: ../../mod/attach.php:8 +#: mod/attach.php:8 msgid "Item not available." msgstr "Пункт не доступен." -#: ../../mod/attach.php:20 +#: mod/attach.php:20 msgid "Item was not found." msgstr "Пункт не был найден." -#: ../../boot.php:749 +#: boot.php:868 msgid "Delete this item?" msgstr "Удалить этот элемент?" -#: ../../boot.php:752 +#: boot.php:871 msgid "show fewer" msgstr "показать меньше" -#: ../../boot.php:1122 +#: boot.php:1292 #, php-format msgid "Update %s failed. See error logs." msgstr "Обновление %s не удалось. Смотрите журнал ошибок." -#: ../../boot.php:1240 +#: boot.php:1404 msgid "Create a New Account" msgstr "Создать новый аккаунт" -#: ../../boot.php:1265 ../../include/nav.php:73 +#: boot.php:1429 include/nav.php:72 msgid "Logout" msgstr "Выход" -#: ../../boot.php:1268 +#: boot.php:1432 msgid "Nickname or Email address: " msgstr "Ник или адрес электронной почты: " -#: ../../boot.php:1269 +#: boot.php:1433 msgid "Password: " msgstr "Пароль: " -#: ../../boot.php:1270 +#: boot.php:1434 msgid "Remember me" msgstr "Запомнить" -#: ../../boot.php:1273 +#: boot.php:1437 msgid "Or login using OpenID: " msgstr "Или зайти с OpenID: " -#: ../../boot.php:1279 +#: boot.php:1443 msgid "Forgot your password?" msgstr "Забыли пароль?" -#: ../../boot.php:1282 +#: boot.php:1446 msgid "Website Terms of Service" msgstr "Правила сайта" -#: ../../boot.php:1283 +#: boot.php:1447 msgid "terms of service" msgstr "правила" -#: ../../boot.php:1285 +#: boot.php:1449 msgid "Website Privacy Policy" msgstr "Политика конфиденциальности сервера" -#: ../../boot.php:1286 +#: boot.php:1450 msgid "privacy policy" msgstr "политика конфиденциальности" -#: ../../boot.php:1419 -msgid "Requested account is not available." -msgstr "Запрашиваемый профиль недоступен." - -#: ../../boot.php:1501 ../../boot.php:1635 -#: ../../include/profile_advanced.php:84 -msgid "Edit profile" -msgstr "Редактировать профиль" - -#: ../../boot.php:1600 -msgid "Message" -msgstr "Сообщение" - -#: ../../boot.php:1606 ../../include/nav.php:175 -msgid "Profiles" -msgstr "Профили" - -#: ../../boot.php:1606 -msgid "Manage/edit profiles" -msgstr "Управление / редактирование профилей" - -#: ../../boot.php:1706 -msgid "Network:" -msgstr "" - -#: ../../boot.php:1736 ../../boot.php:1822 -msgid "g A l F d" -msgstr "g A l F d" - -#: ../../boot.php:1737 ../../boot.php:1823 -msgid "F d" -msgstr "F d" - -#: ../../boot.php:1782 ../../boot.php:1863 -msgid "[today]" -msgstr "[сегодня]" - -#: ../../boot.php:1794 -msgid "Birthday Reminders" -msgstr "Напоминания о днях рождения" - -#: ../../boot.php:1795 -msgid "Birthdays this week:" -msgstr "Дни рождения на этой неделе:" - -#: ../../boot.php:1856 -msgid "[No description]" -msgstr "[без описания]" - -#: ../../boot.php:1874 -msgid "Event Reminders" -msgstr "Напоминания о мероприятиях" - -#: ../../boot.php:1875 -msgid "Events this week:" -msgstr "Мероприятия на этой неделе:" - -#: ../../boot.php:2112 ../../include/nav.php:76 -msgid "Status" -msgstr "Статус" - -#: ../../boot.php:2115 -msgid "Status Messages and Posts" -msgstr "Сообщение статуса и посты" - -#: ../../boot.php:2122 -msgid "Profile Details" -msgstr "Детали профиля" - -#: ../../boot.php:2133 ../../boot.php:2136 ../../include/nav.php:79 -msgid "Videos" -msgstr "Видео" - -#: ../../boot.php:2146 -msgid "Events and Calendar" -msgstr "Календарь и события" - -#: ../../boot.php:2153 -msgid "Only You Can See This" -msgstr "Только вы можете это видеть" - -#: ../../object/Item.php:94 +#: object/Item.php:95 msgid "This entry was edited" msgstr "Эта запись была отредактирована" -#: ../../object/Item.php:208 +#: object/Item.php:191 +msgid "I will attend" +msgstr "" + +#: object/Item.php:191 +msgid "I will not attend" +msgstr "" + +#: object/Item.php:191 +msgid "I might attend" +msgstr "" + +#: object/Item.php:230 msgid "ignore thread" msgstr "" -#: ../../object/Item.php:209 +#: object/Item.php:231 msgid "unignore thread" msgstr "" -#: ../../object/Item.php:210 +#: object/Item.php:232 msgid "toggle ignore status" msgstr "" -#: ../../object/Item.php:213 -msgid "ignored" -msgstr "" - -#: ../../object/Item.php:316 ../../include/conversation.php:666 +#: object/Item.php:345 include/conversation.php:687 msgid "Categories:" msgstr "Категории:" -#: ../../object/Item.php:317 ../../include/conversation.php:667 +#: object/Item.php:346 include/conversation.php:688 msgid "Filed under:" msgstr "В рубрике:" -#: ../../object/Item.php:329 +#: object/Item.php:360 msgid "via" msgstr "через" -#: ../../include/dbstructure.php:26 +#: include/dbstructure.php:26 #, php-format msgid "" "\n" "\t\t\tThe friendica developers released update %s recently,\n" "\t\t\tbut when I tried to install it, something went terribly wrong.\n" "\t\t\tThis needs to be fixed soon and I can't do it alone. Please contact a\n" -"\t\t\tfriendica developer if you can not help me on your own. My database might be invalid." +"\t\t\tfriendica developer if you can not help me on your own. My database " +"might be invalid." msgstr "" -#: ../../include/dbstructure.php:31 +#: include/dbstructure.php:31 #, php-format msgid "" "The error message is\n" "[pre]%s[/pre]" msgstr "" -#: ../../include/dbstructure.php:162 +#: include/dbstructure.php:153 msgid "Errors encountered creating database tables." msgstr "Обнаружены ошибки при создании таблиц базы данных." -#: ../../include/dbstructure.php:220 +#: include/dbstructure.php:230 msgid "Errors encountered performing database changes." msgstr "" -#: ../../include/auth.php:38 +#: include/auth.php:44 msgid "Logged out." msgstr "Выход из системы." -#: ../../include/auth.php:128 ../../include/user.php:67 +#: include/auth.php:134 include/user.php:75 msgid "" "We encountered a problem while logging in with the OpenID you provided. " "Please check the correct spelling of the ID." -msgstr "Мы столкнулись с проблемой при входе с OpenID, который вы указали. Пожалуйста, проверьте правильность написания ID." +msgstr "" +"Мы столкнулись с проблемой при входе с OpenID, который вы указали. " +"Пожалуйста, проверьте правильность написания ID." -#: ../../include/auth.php:128 ../../include/user.php:67 +#: include/auth.php:134 include/user.php:75 msgid "The error message was:" msgstr "Сообщение об ошибке было:" -#: ../../include/contact_widgets.php:6 +#: include/contact_widgets.php:6 msgid "Add New Contact" msgstr "Добавить контакт" -#: ../../include/contact_widgets.php:7 +#: include/contact_widgets.php:7 msgid "Enter address or web location" msgstr "Введите адрес или веб-местонахождение" -#: ../../include/contact_widgets.php:8 +#: include/contact_widgets.php:8 msgid "Example: bob@example.com, http://example.com/barbara" msgstr "Пример: bob@example.com, http://example.com/barbara" -#: ../../include/contact_widgets.php:24 +#: include/contact_widgets.php:24 #, php-format msgid "%d invitation available" msgid_plural "%d invitations available" msgstr[0] "%d приглашение доступно" msgstr[1] "%d приглашений доступно" msgstr[2] "%d приглашений доступно" +msgstr[3] "%d приглашений доступно" -#: ../../include/contact_widgets.php:30 +#: include/contact_widgets.php:30 msgid "Find People" msgstr "Поиск людей" -#: ../../include/contact_widgets.php:31 +#: include/contact_widgets.php:31 msgid "Enter name or interest" msgstr "Введите имя или интерес" -#: ../../include/contact_widgets.php:32 -msgid "Connect/Follow" -msgstr "Подключиться/Следовать" - -#: ../../include/contact_widgets.php:33 +#: include/contact_widgets.php:33 msgid "Examples: Robert Morgenstein, Fishing" msgstr "Примеры: Роберт Morgenstein, Рыбалка" -#: ../../include/contact_widgets.php:36 ../../view/theme/diabook/theme.php:526 +#: include/contact_widgets.php:36 view/theme/diabook/theme.php:526 +#: view/theme/vier/theme.php:206 msgid "Similar Interests" msgstr "Похожие интересы" -#: ../../include/contact_widgets.php:37 +#: include/contact_widgets.php:37 msgid "Random Profile" msgstr "Случайный профиль" -#: ../../include/contact_widgets.php:38 ../../view/theme/diabook/theme.php:528 +#: include/contact_widgets.php:38 view/theme/diabook/theme.php:528 +#: view/theme/vier/theme.php:208 msgid "Invite Friends" msgstr "Пригласить друзей" -#: ../../include/contact_widgets.php:71 +#: include/contact_widgets.php:108 msgid "Networks" msgstr "Сети" -#: ../../include/contact_widgets.php:74 +#: include/contact_widgets.php:111 msgid "All Networks" msgstr "Все сети" -#: ../../include/contact_widgets.php:104 ../../include/features.php:60 +#: include/contact_widgets.php:141 include/features.php:102 msgid "Saved Folders" msgstr "Сохранённые папки" -#: ../../include/contact_widgets.php:107 ../../include/contact_widgets.php:139 +#: include/contact_widgets.php:144 include/contact_widgets.php:176 msgid "Everything" msgstr "Всё" -#: ../../include/contact_widgets.php:136 +#: include/contact_widgets.php:173 msgid "Categories" msgstr "Категории" -#: ../../include/features.php:23 +#: include/contact_widgets.php:237 +#, php-format +msgid "%d contact in common" +msgid_plural "%d contacts in common" +msgstr[0] "%d Контакт" +msgstr[1] "%d Контактов" +msgstr[2] "%d Контактов" +msgstr[3] "%d Контактов" + +#: include/features.php:63 msgid "General Features" msgstr "Основные возможности" -#: ../../include/features.php:25 +#: include/features.php:65 msgid "Multiple Profiles" msgstr "Несколько профилей" -#: ../../include/features.php:25 +#: include/features.php:65 msgid "Ability to create multiple profiles" msgstr "Возможность создания нескольких профилей" -#: ../../include/features.php:30 -msgid "Post Composition Features" +#: include/features.php:66 +msgid "Photo Location" msgstr "" -#: ../../include/features.php:31 +#: include/features.php:66 +msgid "" +"Photo metadata is normally stripped. This extracts the location (if present) " +"prior to stripping metadata and links it to a map." +msgstr "" + +#: include/features.php:71 +msgid "Post Composition Features" +msgstr "Составление сообщений" + +#: include/features.php:72 msgid "Richtext Editor" msgstr "Редактор RTF" -#: ../../include/features.php:31 +#: include/features.php:72 msgid "Enable richtext editor" msgstr "Включить редактор RTF" -#: ../../include/features.php:32 +#: include/features.php:73 msgid "Post Preview" -msgstr "предварительный просмотр" +msgstr "Предварительный просмотр" -#: ../../include/features.php:32 +#: include/features.php:73 msgid "Allow previewing posts and comments before publishing them" msgstr "Разрешить предпросмотр сообщения и комментария перед их публикацией" -#: ../../include/features.php:33 +#: include/features.php:74 msgid "Auto-mention Forums" msgstr "" -#: ../../include/features.php:33 +#: include/features.php:74 msgid "" "Add/remove mention when a fourm page is selected/deselected in ACL window." msgstr "" -#: ../../include/features.php:38 +#: include/features.php:79 msgid "Network Sidebar Widgets" msgstr "Виджет боковой панели \"Сеть\"" -#: ../../include/features.php:39 +#: include/features.php:80 msgid "Search by Date" msgstr "Поиск по датам" -#: ../../include/features.php:39 +#: include/features.php:80 msgid "Ability to select posts by date ranges" msgstr "Возможность выбора постов по диапазону дат" -#: ../../include/features.php:40 +#: include/features.php:81 include/features.php:111 +msgid "List Forums" +msgstr "" + +#: include/features.php:81 +msgid "Enable widget to display the forums your are connected with" +msgstr "" + +#: include/features.php:82 msgid "Group Filter" msgstr "Фильтр групп" -#: ../../include/features.php:40 +#: include/features.php:82 msgid "Enable widget to display Network posts only from selected group" -msgstr "Включить виджет для отображения сообщений сети только от выбранной группы" +msgstr "" +"Включить виджет для отображения сообщений сети только от выбранной группы" -#: ../../include/features.php:41 +#: include/features.php:83 msgid "Network Filter" msgstr "Фильтр сети" -#: ../../include/features.php:41 +#: include/features.php:83 msgid "Enable widget to display Network posts only from selected network" -msgstr "Включить виджет для отображения сообщений сети только от выбранной сети" +msgstr "" +"Включить виджет для отображения сообщений сети только от выбранной сети" -#: ../../include/features.php:42 +#: include/features.php:84 msgid "Save search terms for re-use" msgstr "Сохранить условия поиска для повторного использования" -#: ../../include/features.php:47 +#: include/features.php:89 msgid "Network Tabs" msgstr "Сетевые вкладки" -#: ../../include/features.php:48 +#: include/features.php:90 msgid "Network Personal Tab" msgstr "Персональные сетевые вкладки" -#: ../../include/features.php:48 +#: include/features.php:90 msgid "Enable tab to display only Network posts that you've interacted on" -msgstr "Включить вкладку для отображения только сообщений сети, с которой вы взаимодействовали" +msgstr "" +"Включить вкладку для отображения только сообщений сети, с которой вы " +"взаимодействовали" -#: ../../include/features.php:49 +#: include/features.php:91 msgid "Network New Tab" msgstr "Новая вкладка сеть" -#: ../../include/features.php:49 +#: include/features.php:91 msgid "Enable tab to display only new Network posts (from the last 12 hours)" -msgstr "Включить вкладку для отображения только новых сообщений сети (за последние 12 часов)" +msgstr "" +"Включить вкладку для отображения только новых сообщений сети (за последние " +"12 часов)" -#: ../../include/features.php:50 +#: include/features.php:92 msgid "Network Shared Links Tab" msgstr "Вкладка shared ссылок сети" -#: ../../include/features.php:50 +#: include/features.php:92 msgid "Enable tab to display only Network posts with links in them" -msgstr "Включить вкладку для отображения только сообщений сети со ссылками на них" +msgstr "" +"Включить вкладку для отображения только сообщений сети со ссылками на них" -#: ../../include/features.php:55 +#: include/features.php:97 msgid "Post/Comment Tools" msgstr "Инструменты пост/комментарий" -#: ../../include/features.php:56 +#: include/features.php:98 msgid "Multiple Deletion" msgstr "Множественное удаление" -#: ../../include/features.php:56 +#: include/features.php:98 msgid "Select and delete multiple posts/comments at once" msgstr "Выбрать и удалить несколько постов/комментариев одновременно." -#: ../../include/features.php:57 +#: include/features.php:99 msgid "Edit Sent Posts" msgstr "Редактировать отправленные посты" -#: ../../include/features.php:57 +#: include/features.php:99 msgid "Edit and correct posts and comments after sending" msgstr "Редактировать и править посты и комментарии после отправления" -#: ../../include/features.php:58 +#: include/features.php:100 msgid "Tagging" msgstr "Отмеченное" -#: ../../include/features.php:58 +#: include/features.php:100 msgid "Ability to tag existing posts" msgstr "Возможность отмечать существующие посты" -#: ../../include/features.php:59 +#: include/features.php:101 msgid "Post Categories" msgstr "Категории постов" -#: ../../include/features.php:59 +#: include/features.php:101 msgid "Add categories to your posts" msgstr "Добавить категории вашего поста" -#: ../../include/features.php:60 +#: include/features.php:102 msgid "Ability to file posts under folders" msgstr "" -#: ../../include/features.php:61 +#: include/features.php:103 msgid "Dislike Posts" msgstr "Посты дизлайк" -#: ../../include/features.php:61 +#: include/features.php:103 msgid "Ability to dislike posts/comments" msgstr "Возможность дизлайка постов/комментариев" -#: ../../include/features.php:62 +#: include/features.php:104 msgid "Star Posts" msgstr "Популярные посты" -#: ../../include/features.php:62 +#: include/features.php:104 msgid "Ability to mark special posts with a star indicator" msgstr "Возможность отметить специальные сообщения индикатором популярности" -#: ../../include/features.php:63 +#: include/features.php:105 msgid "Mute Post Notifications" msgstr "" -#: ../../include/features.php:63 +#: include/features.php:105 msgid "Ability to mute notifications for a thread" msgstr "" -#: ../../include/follow.php:32 +#: include/features.php:110 +msgid "Advanced Profile Settings" +msgstr "Расширенные настройки профиля" + +#: include/features.php:111 +msgid "Show visitors public community forums at the Advanced Profile Page" +msgstr "" + +#: include/follow.php:77 msgid "Connect URL missing." msgstr "Connect-URL отсутствует." -#: ../../include/follow.php:59 +#: include/follow.php:104 msgid "" "This site is not configured to allow communications with other networks." msgstr "Данный сайт не настроен так, чтобы держать связь с другими сетями." -#: ../../include/follow.php:60 ../../include/follow.php:80 +#: include/follow.php:105 include/follow.php:125 msgid "No compatible communication protocols or feeds were discovered." msgstr "Обнаружены несовместимые протоколы связи или каналы." -#: ../../include/follow.php:78 +#: include/follow.php:123 msgid "The profile address specified does not provide adequate information." msgstr "Указанный адрес профиля не дает адекватной информации." -#: ../../include/follow.php:82 +#: include/follow.php:127 msgid "An author or name was not found." msgstr "Автор или имя не найдены." -#: ../../include/follow.php:84 +#: include/follow.php:129 msgid "No browser URL could be matched to this address." msgstr "Нет URL браузера, который соответствует этому адресу." -#: ../../include/follow.php:86 +#: include/follow.php:131 msgid "" "Unable to match @-style Identity Address with a known protocol or email " "contact." msgstr "" -#: ../../include/follow.php:87 +#: include/follow.php:132 msgid "Use mailto: in front of address to force email check." msgstr "Bcgjkmpeqnt mailto: перед адресом для быстрого доступа к email." -#: ../../include/follow.php:93 +#: include/follow.php:138 msgid "" "The profile address specified belongs to a network which has been disabled " "on this site." msgstr "Указанный адрес профиля принадлежит сети, недоступной на этом сайта." -#: ../../include/follow.php:103 +#: include/follow.php:148 msgid "" "Limited profile. This person will be unable to receive direct/personal " "notifications from you." -msgstr "Ограниченный профиль. Этот человек не сможет получить прямые / личные уведомления от вас." +msgstr "" +"Ограниченный профиль. Этот человек не сможет получить прямые / личные " +"уведомления от вас." -#: ../../include/follow.php:205 +#: include/follow.php:249 msgid "Unable to retrieve contact information." msgstr "Невозможно получить контактную информацию." -#: ../../include/follow.php:258 +#: include/follow.php:302 msgid "following" msgstr "следует" -#: ../../include/group.php:25 +#: include/group.php:25 msgid "" "A deleted group with this name was revived. Existing item permissions " "<strong>may</strong> apply to this group and any future members. If this is " "not what you intended, please create another group with a different name." -msgstr "Удаленная группа с таким названием была восстановлена. Существующие права доступа <strong>могут</strong> применяться к этой группе и любым будущим участникам. Если это не то, что вы хотели, пожалуйста, создайте еще одну группу с другим названием." +msgstr "" +"Удаленная группа с таким названием была восстановлена. Существующие права " +"доступа <strong>могут</strong> применяться к этой группе и любым будущим " +"участникам. Если это не то, что вы хотели, пожалуйста, создайте еще одну " +"группу с другим названием." -#: ../../include/group.php:207 +#: include/group.php:209 msgid "Default privacy group for new contacts" msgstr "Группа доступа по умолчанию для новых контактов" -#: ../../include/group.php:226 +#: include/group.php:239 msgid "Everybody" msgstr "Каждый" -#: ../../include/group.php:249 +#: include/group.php:262 msgid "edit" msgstr "редактировать" -#: ../../include/group.php:271 +#: include/group.php:285 +msgid "Edit groups" +msgstr "" + +#: include/group.php:287 msgid "Edit group" msgstr "Редактировать группу" -#: ../../include/group.php:272 +#: include/group.php:288 msgid "Create a new group" msgstr "Создать новую группу" -#: ../../include/group.php:273 +#: include/group.php:291 msgid "Contacts not in any group" msgstr "Контакты не состоят в группе" -#: ../../include/datetime.php:43 ../../include/datetime.php:45 +#: include/datetime.php:43 include/datetime.php:45 msgid "Miscellaneous" msgstr "Разное" -#: ../../include/datetime.php:153 ../../include/datetime.php:290 -msgid "year" -msgstr "год" +#: include/datetime.php:141 +msgid "YYYY-MM-DD or MM-DD" +msgstr "" -#: ../../include/datetime.php:158 ../../include/datetime.php:291 -msgid "month" -msgstr "мес." - -#: ../../include/datetime.php:163 ../../include/datetime.php:293 -msgid "day" -msgstr "день" - -#: ../../include/datetime.php:276 +#: include/datetime.php:271 msgid "never" msgstr "никогда" -#: ../../include/datetime.php:282 +#: include/datetime.php:277 msgid "less than a second ago" msgstr "менее сек. назад" -#: ../../include/datetime.php:290 +#: include/datetime.php:287 +msgid "year" +msgstr "год" + +#: include/datetime.php:287 msgid "years" msgstr "лет" -#: ../../include/datetime.php:291 +#: include/datetime.php:288 msgid "months" msgstr "мес." -#: ../../include/datetime.php:292 -msgid "week" -msgstr "неделя" - -#: ../../include/datetime.php:292 +#: include/datetime.php:289 msgid "weeks" msgstr "недель" -#: ../../include/datetime.php:293 +#: include/datetime.php:290 msgid "days" msgstr "дней" -#: ../../include/datetime.php:294 +#: include/datetime.php:291 msgid "hour" msgstr "час" -#: ../../include/datetime.php:294 +#: include/datetime.php:291 msgid "hours" msgstr "час." -#: ../../include/datetime.php:295 +#: include/datetime.php:292 msgid "minute" msgstr "минута" -#: ../../include/datetime.php:295 +#: include/datetime.php:292 msgid "minutes" msgstr "мин." -#: ../../include/datetime.php:296 +#: include/datetime.php:293 msgid "second" msgstr "секунда" -#: ../../include/datetime.php:296 +#: include/datetime.php:293 msgid "seconds" msgstr "сек." -#: ../../include/datetime.php:305 +#: include/datetime.php:302 #, php-format msgid "%1$d %2$s ago" msgstr "%1$d %2$s назад" -#: ../../include/datetime.php:477 ../../include/items.php:2211 +#: include/datetime.php:474 include/items.php:2500 #, php-format msgid "%s's birthday" msgstr "день рождения %s" -#: ../../include/datetime.php:478 ../../include/items.php:2212 +#: include/datetime.php:475 include/items.php:2501 #, php-format msgid "Happy Birthday %s" msgstr "С днём рождения %s" -#: ../../include/acl_selectors.php:333 -msgid "Visible to everybody" -msgstr "Видимо всем" +#: include/identity.php:42 +msgid "Requested account is not available." +msgstr "Запрашиваемый профиль недоступен." -#: ../../include/acl_selectors.php:334 ../../view/theme/diabook/config.php:142 -#: ../../view/theme/diabook/theme.php:621 -msgid "show" -msgstr "показывать" +#: include/identity.php:95 include/identity.php:284 include/identity.php:662 +msgid "Edit profile" +msgstr "Редактировать профиль" -#: ../../include/acl_selectors.php:335 ../../view/theme/diabook/config.php:142 -#: ../../view/theme/diabook/theme.php:621 -msgid "don't show" -msgstr "не показывать" - -#: ../../include/message.php:15 ../../include/message.php:172 -msgid "[no subject]" -msgstr "[без темы]" - -#: ../../include/Contact.php:115 -msgid "stopped following" -msgstr "остановлено следование" - -#: ../../include/Contact.php:228 ../../include/conversation.php:882 -msgid "Poke" +#: include/identity.php:244 +msgid "Atom feed" msgstr "" -#: ../../include/Contact.php:229 ../../include/conversation.php:876 -msgid "View Status" -msgstr "Просмотреть статус" +#: include/identity.php:249 +msgid "Message" +msgstr "Сообщение" -#: ../../include/Contact.php:230 ../../include/conversation.php:877 -msgid "View Profile" -msgstr "Просмотреть профиль" +#: include/identity.php:255 include/nav.php:185 +msgid "Profiles" +msgstr "Профили" -#: ../../include/Contact.php:231 ../../include/conversation.php:878 -msgid "View Photos" -msgstr "Просмотреть фото" +#: include/identity.php:255 +msgid "Manage/edit profiles" +msgstr "Управление / редактирование профилей" -#: ../../include/Contact.php:232 ../../include/Contact.php:255 -#: ../../include/conversation.php:879 -msgid "Network Posts" -msgstr "Посты сети" +#: include/identity.php:425 include/identity.php:509 +msgid "g A l F d" +msgstr "g A l F d" -#: ../../include/Contact.php:233 ../../include/Contact.php:255 -#: ../../include/conversation.php:880 -msgid "Edit Contact" -msgstr "Редактировать контакт" +#: include/identity.php:426 include/identity.php:510 +msgid "F d" +msgstr "F d" -#: ../../include/Contact.php:234 -msgid "Drop Contact" -msgstr "Удалить контакт" +#: include/identity.php:471 include/identity.php:556 +msgid "[today]" +msgstr "[сегодня]" -#: ../../include/Contact.php:235 ../../include/Contact.php:255 -#: ../../include/conversation.php:881 -msgid "Send PM" -msgstr "Отправить ЛС" +#: include/identity.php:483 +msgid "Birthday Reminders" +msgstr "Напоминания о днях рождения" -#: ../../include/security.php:22 -msgid "Welcome " -msgstr "Добро пожаловать, " +#: include/identity.php:484 +msgid "Birthdays this week:" +msgstr "Дни рождения на этой неделе:" -#: ../../include/security.php:23 -msgid "Please upload a profile photo." -msgstr "Пожалуйста, загрузите фотографию профиля." +#: include/identity.php:543 +msgid "[No description]" +msgstr "[без описания]" -#: ../../include/security.php:26 -msgid "Welcome back " -msgstr "Добро пожаловать обратно, " +#: include/identity.php:567 +msgid "Event Reminders" +msgstr "Напоминания о мероприятиях" -#: ../../include/security.php:366 -msgid "" -"The form security token was not correct. This probably happened because the " -"form has been opened for too long (>3 hours) before submitting it." -msgstr "Ключ формы безопасности неправильный. Вероятно, это произошло потому, что форма была открыта слишком долго (более 3 часов) до её отправки." +#: include/identity.php:568 +msgid "Events this week:" +msgstr "Мероприятия на этой неделе:" -#: ../../include/conversation.php:118 ../../include/conversation.php:246 -#: ../../include/text.php:1966 ../../view/theme/diabook/theme.php:463 +#: include/identity.php:595 +msgid "j F, Y" +msgstr "j F, Y" + +#: include/identity.php:596 +msgid "j F" +msgstr "j F" + +#: include/identity.php:603 +msgid "Birthday:" +msgstr "День рождения:" + +#: include/identity.php:607 +msgid "Age:" +msgstr "Возраст:" + +#: include/identity.php:616 +#, php-format +msgid "for %1$d %2$s" +msgstr "" + +#: include/identity.php:629 +msgid "Religion:" +msgstr "Религия:" + +#: include/identity.php:633 +msgid "Hobbies/Interests:" +msgstr "Хобби / Интересы:" + +#: include/identity.php:640 +msgid "Contact information and Social Networks:" +msgstr "Информация о контакте и социальных сетях:" + +#: include/identity.php:642 +msgid "Musical interests:" +msgstr "Музыкальные интересы:" + +#: include/identity.php:644 +msgid "Books, literature:" +msgstr "Книги, литература:" + +#: include/identity.php:646 +msgid "Television:" +msgstr "Телевидение:" + +#: include/identity.php:648 +msgid "Film/dance/culture/entertainment:" +msgstr "Кино / Танцы / Культура / Развлечения:" + +#: include/identity.php:650 +msgid "Love/Romance:" +msgstr "Любовь / Романтика:" + +#: include/identity.php:652 +msgid "Work/employment:" +msgstr "Работа / Занятость:" + +#: include/identity.php:654 +msgid "School/education:" +msgstr "Школа / Образование:" + +#: include/identity.php:658 +msgid "Forums:" +msgstr "" + +#: include/identity.php:710 include/identity.php:713 include/nav.php:78 +msgid "Videos" +msgstr "Видео" + +#: include/identity.php:725 include/nav.php:140 +msgid "Events and Calendar" +msgstr "Календарь и события" + +#: include/identity.php:733 +msgid "Only You Can See This" +msgstr "Только вы можете это видеть" + +#: include/like.php:167 include/conversation.php:122 +#: include/conversation.php:258 include/text.php:1998 +#: view/theme/diabook/theme.php:463 msgid "event" msgstr "мероприятие" -#: ../../include/conversation.php:207 +#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2185 +#: view/theme/diabook/theme.php:480 #, php-format -msgid "%1$s poked %2$s" +msgid "%1$s likes %2$s's %3$s" +msgstr "%1$s нравится %3$s от %2$s " + +#: include/like.php:186 include/conversation.php:144 +#, php-format +msgid "%1$s doesn't like %2$s's %3$s" +msgstr "%1$s не нравится %3$s от %2$s " + +#: include/like.php:188 +#, php-format +msgid "%1$s is attending %2$s's %3$s" msgstr "" -#: ../../include/conversation.php:211 ../../include/text.php:1005 -msgid "poked" +#: include/like.php:190 +#, php-format +msgid "%1$s is not attending %2$s's %3$s" msgstr "" -#: ../../include/conversation.php:291 -msgid "post/item" -msgstr "пост/элемент" - -#: ../../include/conversation.php:292 +#: include/like.php:192 #, php-format -msgid "%1$s marked %2$s's %3$s as favorite" -msgstr "%1$s пометил %2$s %3$s как Фаворит" - -#: ../../include/conversation.php:772 -msgid "remove" -msgstr "удалить" - -#: ../../include/conversation.php:776 -msgid "Delete Selected Items" -msgstr "Удалить выбранные позиции" - -#: ../../include/conversation.php:875 -msgid "Follow Thread" +msgid "%1$s may attend %2$s's %3$s" msgstr "" -#: ../../include/conversation.php:944 -#, php-format -msgid "%s likes this." -msgstr "%s нравится это." - -#: ../../include/conversation.php:944 -#, php-format -msgid "%s doesn't like this." -msgstr "%s не нравится это." - -#: ../../include/conversation.php:949 -#, php-format -msgid "<span %1$s>%2$d people</span> like this" -msgstr "<span %1$s>%2$d людям</span> нравится это" - -#: ../../include/conversation.php:952 -#, php-format -msgid "<span %1$s>%2$d people</span> don't like this" -msgstr "<span %1$s>%2$d людям</span> не нравится это" - -#: ../../include/conversation.php:966 -msgid "and" -msgstr "и" - -#: ../../include/conversation.php:972 -#, php-format -msgid ", and %d other people" -msgstr ", и %d других чел." - -#: ../../include/conversation.php:974 -#, php-format -msgid "%s like this." -msgstr "%s нравится это." - -#: ../../include/conversation.php:974 -#, php-format -msgid "%s don't like this." -msgstr "%s не нравится это." - -#: ../../include/conversation.php:1001 ../../include/conversation.php:1019 -msgid "Visible to <strong>everybody</strong>" -msgstr "Видимое <strong>всем</strong>" - -#: ../../include/conversation.php:1003 ../../include/conversation.php:1021 -msgid "Please enter a video link/URL:" -msgstr "Введите ссылку на видео link/URL:" - -#: ../../include/conversation.php:1004 ../../include/conversation.php:1022 -msgid "Please enter an audio link/URL:" -msgstr "Введите ссылку на аудио link/URL:" - -#: ../../include/conversation.php:1005 ../../include/conversation.php:1023 -msgid "Tag term:" -msgstr "" - -#: ../../include/conversation.php:1007 ../../include/conversation.php:1025 -msgid "Where are you right now?" -msgstr "И где вы сейчас?" - -#: ../../include/conversation.php:1008 -msgid "Delete item(s)?" -msgstr "Удалить елемент(ты)?" - -#: ../../include/conversation.php:1051 +#: include/acl_selectors.php:325 msgid "Post to Email" msgstr "Отправить на Email" -#: ../../include/conversation.php:1056 +#: include/acl_selectors.php:330 #, php-format msgid "Connectors disabled, since \"%s\" is enabled." msgstr "" -#: ../../include/conversation.php:1111 +#: include/acl_selectors.php:336 +msgid "Visible to everybody" +msgstr "Видимо всем" + +#: include/acl_selectors.php:337 view/theme/diabook/config.php:142 +#: view/theme/diabook/theme.php:621 view/theme/vier/config.php:103 +msgid "show" +msgstr "показывать" + +#: include/acl_selectors.php:338 view/theme/diabook/config.php:142 +#: view/theme/diabook/theme.php:621 view/theme/vier/config.php:103 +msgid "don't show" +msgstr "не показывать" + +#: include/acl_selectors.php:348 +msgid "Close" +msgstr "Закрыть" + +#: include/message.php:15 include/message.php:173 +msgid "[no subject]" +msgstr "[без темы]" + +#: include/Contact.php:119 +msgid "stopped following" +msgstr "остановлено следование" + +#: include/Contact.php:337 include/conversation.php:911 +msgid "View Status" +msgstr "Просмотреть статус" + +#: include/Contact.php:339 include/conversation.php:913 +msgid "View Photos" +msgstr "Просмотреть фото" + +#: include/Contact.php:340 include/conversation.php:914 +msgid "Network Posts" +msgstr "Посты сети" + +#: include/Contact.php:341 include/conversation.php:915 +msgid "Edit Contact" +msgstr "Редактировать контакт" + +#: include/Contact.php:342 +msgid "Drop Contact" +msgstr "Удалить контакт" + +#: include/Contact.php:343 include/conversation.php:916 +msgid "Send PM" +msgstr "Отправить ЛС" + +#: include/Contact.php:344 include/conversation.php:920 +msgid "Poke" +msgstr "" + +#: include/security.php:22 +msgid "Welcome " +msgstr "Добро пожаловать, " + +#: include/security.php:23 +msgid "Please upload a profile photo." +msgstr "Пожалуйста, загрузите фотографию профиля." + +#: include/security.php:26 +msgid "Welcome back " +msgstr "Добро пожаловать обратно, " + +#: include/security.php:375 +msgid "" +"The form security token was not correct. This probably happened because the " +"form has been opened for too long (>3 hours) before submitting it." +msgstr "" +"Ключ формы безопасности неправильный. Вероятно, это произошло потому, что " +"форма была открыта слишком долго (более 3 часов) до её отправки." + +#: include/conversation.php:147 +#, php-format +msgid "%1$s attends %2$s's %3$s" +msgstr "" + +#: include/conversation.php:150 +#, php-format +msgid "%1$s doesn't attend %2$s's %3$s" +msgstr "" + +#: include/conversation.php:153 +#, php-format +msgid "%1$s attends maybe %2$s's %3$s" +msgstr "" + +#: include/conversation.php:219 +#, php-format +msgid "%1$s poked %2$s" +msgstr "" + +#: include/conversation.php:303 +msgid "post/item" +msgstr "пост/элемент" + +#: include/conversation.php:304 +#, php-format +msgid "%1$s marked %2$s's %3$s as favorite" +msgstr "%1$s пометил %2$s %3$s как Фаворит" + +#: include/conversation.php:792 +msgid "remove" +msgstr "удалить" + +#: include/conversation.php:796 +msgid "Delete Selected Items" +msgstr "Удалить выбранные позиции" + +#: include/conversation.php:910 +msgid "Follow Thread" +msgstr "" + +#: include/conversation.php:1034 +#, php-format +msgid "%s likes this." +msgstr "%s нравится это." + +#: include/conversation.php:1037 +#, php-format +msgid "%s doesn't like this." +msgstr "%s не нравится это." + +#: include/conversation.php:1040 +#, php-format +msgid "%s attends." +msgstr "" + +#: include/conversation.php:1043 +#, php-format +msgid "%s doesn't attend." +msgstr "" + +#: include/conversation.php:1046 +#, php-format +msgid "%s attends maybe." +msgstr "" + +#: include/conversation.php:1056 +msgid "and" +msgstr "и" + +#: include/conversation.php:1062 +#, php-format +msgid ", and %d other people" +msgstr ", и %d других чел." + +#: include/conversation.php:1071 +#, php-format +msgid "<span %1$s>%2$d people</span> like this" +msgstr "<span %1$s>%2$d людям</span> нравится это" + +#: include/conversation.php:1072 +#, php-format +msgid "%s like this." +msgstr "" + +#: include/conversation.php:1075 +#, php-format +msgid "<span %1$s>%2$d people</span> don't like this" +msgstr "<span %1$s>%2$d людям</span> не нравится это" + +#: include/conversation.php:1076 +#, php-format +msgid "%s don't like this." +msgstr "" + +#: include/conversation.php:1079 +#, php-format +msgid "<span %1$s>%2$d people</span> attend" +msgstr "" + +#: include/conversation.php:1080 +#, php-format +msgid "%s attend." +msgstr "" + +#: include/conversation.php:1083 +#, php-format +msgid "<span %1$s>%2$d people</span> don't attend" +msgstr "" + +#: include/conversation.php:1084 +#, php-format +msgid "%s don't attend." +msgstr "" + +#: include/conversation.php:1087 +#, php-format +msgid "<span %1$s>%2$d people</span> anttend maybe" +msgstr "" + +#: include/conversation.php:1088 +#, php-format +msgid "%s anttend maybe." +msgstr "" + +#: include/conversation.php:1127 include/conversation.php:1145 +msgid "Visible to <strong>everybody</strong>" +msgstr "Видимое <strong>всем</strong>" + +#: include/conversation.php:1129 include/conversation.php:1147 +msgid "Please enter a video link/URL:" +msgstr "Введите ссылку на видео link/URL:" + +#: include/conversation.php:1130 include/conversation.php:1148 +msgid "Please enter an audio link/URL:" +msgstr "Введите ссылку на аудио link/URL:" + +#: include/conversation.php:1131 include/conversation.php:1149 +msgid "Tag term:" +msgstr "" + +#: include/conversation.php:1133 include/conversation.php:1151 +msgid "Where are you right now?" +msgstr "И где вы сейчас?" + +#: include/conversation.php:1134 +msgid "Delete item(s)?" +msgstr "Удалить елемент(ты)?" + +#: include/conversation.php:1203 msgid "permissions" msgstr "разрешения" -#: ../../include/conversation.php:1135 +#: include/conversation.php:1226 msgid "Post to Groups" msgstr "Пост для групп" -#: ../../include/conversation.php:1136 +#: include/conversation.php:1227 msgid "Post to Contacts" msgstr "Пост для контактов" -#: ../../include/conversation.php:1137 +#: include/conversation.php:1228 msgid "Private post" msgstr "Личное сообщение" -#: ../../include/network.php:895 +#: include/conversation.php:1385 +msgid "View all" +msgstr "" + +#: include/conversation.php:1407 +msgid "Like" +msgid_plural "Likes" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +#: include/conversation.php:1410 +msgid "Dislike" +msgid_plural "Dislikes" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +#: include/conversation.php:1416 +msgid "Not Attending" +msgid_plural "Not Attending" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +#: include/conversation.php:1419 include/profile_selectors.php:6 +msgid "Undecided" +msgid_plural "Undecided" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +#: include/forums.php:105 include/text.php:1015 include/nav.php:126 +#: view/theme/vier/theme.php:259 +msgid "Forums" +msgstr "" + +#: include/forums.php:107 view/theme/vier/theme.php:261 +msgid "External link to forum" +msgstr "" + +#: include/network.php:967 msgid "view full size" msgstr "посмотреть в полный размер" -#: ../../include/text.php:297 +#: include/text.php:303 msgid "newer" msgstr "новее" -#: ../../include/text.php:299 +#: include/text.php:305 msgid "older" msgstr "старее" -#: ../../include/text.php:304 +#: include/text.php:310 msgid "prev" msgstr "пред." -#: ../../include/text.php:306 +#: include/text.php:312 msgid "first" msgstr "первый" -#: ../../include/text.php:338 +#: include/text.php:344 msgid "last" msgstr "последний" -#: ../../include/text.php:341 +#: include/text.php:347 msgid "next" msgstr "след." -#: ../../include/text.php:855 +#: include/text.php:402 +msgid "Loading more entries..." +msgstr "" + +#: include/text.php:403 +msgid "The end" +msgstr "" + +#: include/text.php:894 msgid "No contacts" msgstr "Нет контактов" -#: ../../include/text.php:864 +#: include/text.php:909 #, php-format msgid "%d Contact" msgid_plural "%d Contacts" msgstr[0] "%d контакт" msgstr[1] "%d контактов" msgstr[2] "%d контактов" +msgstr[3] "%d контактов" -#: ../../include/text.php:1005 +#: include/text.php:921 +msgid "View Contacts" +msgstr "Просмотр контактов" + +#: include/text.php:1010 include/nav.php:121 +msgid "Full Text" +msgstr "Контент" + +#: include/text.php:1011 include/nav.php:122 +msgid "Tags" +msgstr "Тэги" + +#: include/text.php:1066 msgid "poke" msgstr "poke" -#: ../../include/text.php:1006 +#: include/text.php:1066 +msgid "poked" +msgstr "" + +#: include/text.php:1067 msgid "ping" msgstr "пинг" -#: ../../include/text.php:1006 +#: include/text.php:1067 msgid "pinged" msgstr "пингуется" -#: ../../include/text.php:1007 +#: include/text.php:1068 msgid "prod" msgstr "" -#: ../../include/text.php:1007 +#: include/text.php:1068 msgid "prodded" msgstr "" -#: ../../include/text.php:1008 +#: include/text.php:1069 msgid "slap" msgstr "" -#: ../../include/text.php:1008 +#: include/text.php:1069 msgid "slapped" msgstr "" -#: ../../include/text.php:1009 +#: include/text.php:1070 msgid "finger" msgstr "" -#: ../../include/text.php:1009 +#: include/text.php:1070 msgid "fingered" msgstr "" -#: ../../include/text.php:1010 +#: include/text.php:1071 msgid "rebuff" msgstr "" -#: ../../include/text.php:1010 +#: include/text.php:1071 msgid "rebuffed" msgstr "" -#: ../../include/text.php:1024 +#: include/text.php:1085 msgid "happy" msgstr "" -#: ../../include/text.php:1025 +#: include/text.php:1086 msgid "sad" msgstr "" -#: ../../include/text.php:1026 +#: include/text.php:1087 msgid "mellow" msgstr "" -#: ../../include/text.php:1027 +#: include/text.php:1088 msgid "tired" msgstr "" -#: ../../include/text.php:1028 +#: include/text.php:1089 msgid "perky" msgstr "" -#: ../../include/text.php:1029 +#: include/text.php:1090 msgid "angry" msgstr "" -#: ../../include/text.php:1030 +#: include/text.php:1091 msgid "stupified" msgstr "" -#: ../../include/text.php:1031 +#: include/text.php:1092 msgid "puzzled" msgstr "" -#: ../../include/text.php:1032 +#: include/text.php:1093 msgid "interested" msgstr "" -#: ../../include/text.php:1033 +#: include/text.php:1094 msgid "bitter" msgstr "" -#: ../../include/text.php:1034 +#: include/text.php:1095 msgid "cheerful" msgstr "" -#: ../../include/text.php:1035 +#: include/text.php:1096 msgid "alive" msgstr "" -#: ../../include/text.php:1036 +#: include/text.php:1097 msgid "annoyed" msgstr "" -#: ../../include/text.php:1037 +#: include/text.php:1098 msgid "anxious" msgstr "" -#: ../../include/text.php:1038 +#: include/text.php:1099 msgid "cranky" msgstr "" -#: ../../include/text.php:1039 +#: include/text.php:1100 msgid "disturbed" msgstr "" -#: ../../include/text.php:1040 +#: include/text.php:1101 msgid "frustrated" msgstr "" -#: ../../include/text.php:1041 +#: include/text.php:1102 msgid "motivated" msgstr "" -#: ../../include/text.php:1042 +#: include/text.php:1103 msgid "relaxed" msgstr "" -#: ../../include/text.php:1043 +#: include/text.php:1104 msgid "surprised" msgstr "" -#: ../../include/text.php:1213 -msgid "Monday" -msgstr "Понедельник" - -#: ../../include/text.php:1213 -msgid "Tuesday" -msgstr "Вторник" - -#: ../../include/text.php:1213 -msgid "Wednesday" -msgstr "Среда" - -#: ../../include/text.php:1213 -msgid "Thursday" -msgstr "Четверг" - -#: ../../include/text.php:1213 -msgid "Friday" -msgstr "Пятница" - -#: ../../include/text.php:1213 -msgid "Saturday" -msgstr "Суббота" - -#: ../../include/text.php:1213 -msgid "Sunday" -msgstr "Воскресенье" - -#: ../../include/text.php:1217 -msgid "January" -msgstr "Январь" - -#: ../../include/text.php:1217 -msgid "February" -msgstr "Февраль" - -#: ../../include/text.php:1217 -msgid "March" -msgstr "Март" - -#: ../../include/text.php:1217 -msgid "April" -msgstr "Апрель" - -#: ../../include/text.php:1217 -msgid "May" -msgstr "Май" - -#: ../../include/text.php:1217 -msgid "June" -msgstr "Июнь" - -#: ../../include/text.php:1217 -msgid "July" -msgstr "Июль" - -#: ../../include/text.php:1217 -msgid "August" -msgstr "Август" - -#: ../../include/text.php:1217 -msgid "September" -msgstr "Сентябрь" - -#: ../../include/text.php:1217 -msgid "October" -msgstr "Октябрь" - -#: ../../include/text.php:1217 -msgid "November" -msgstr "Ноябрь" - -#: ../../include/text.php:1217 -msgid "December" -msgstr "Декабрь" - -#: ../../include/text.php:1437 +#: include/text.php:1504 msgid "bytes" msgstr "байт" -#: ../../include/text.php:1461 ../../include/text.php:1473 +#: include/text.php:1536 include/text.php:1548 msgid "Click to open/close" msgstr "Нажмите, чтобы открыть / закрыть" -#: ../../include/text.php:1702 ../../include/user.php:247 -#: ../../view/theme/duepuntozero/config.php:44 -msgid "default" -msgstr "значение по умолчанию" +#: include/text.php:1722 +msgid "View on separate page" +msgstr "" -#: ../../include/text.php:1714 -msgid "Select an alternate language" -msgstr "Выбор альтернативного языка" +#: include/text.php:1723 +msgid "view on separate page" +msgstr "" -#: ../../include/text.php:1970 +#: include/text.php:2002 msgid "activity" msgstr "активность" -#: ../../include/text.php:1973 +#: include/text.php:2005 msgid "post" msgstr "сообщение" -#: ../../include/text.php:2141 +#: include/text.php:2173 msgid "Item filed" msgstr "" -#: ../../include/bbcode.php:428 ../../include/bbcode.php:1047 -#: ../../include/bbcode.php:1048 +#: include/bbcode.php:482 include/bbcode.php:1157 include/bbcode.php:1158 msgid "Image/photo" msgstr "Изображение / Фото" -#: ../../include/bbcode.php:528 +#: include/bbcode.php:595 #, php-format msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s" msgstr "" -#: ../../include/bbcode.php:562 +#: include/bbcode.php:629 #, php-format msgid "" -"<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a " -"href=\"%s\" target=\"_blank\">post</a>" +"<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a href=" +"\"%s\" target=\"_blank\">post</a>" msgstr "" -#: ../../include/bbcode.php:1011 ../../include/bbcode.php:1031 +#: include/bbcode.php:1117 include/bbcode.php:1137 msgid "$1 wrote:" msgstr "$1 написал:" -#: ../../include/bbcode.php:1056 ../../include/bbcode.php:1057 +#: include/bbcode.php:1166 include/bbcode.php:1167 msgid "Encrypted content" msgstr "Зашифрованный контент" -#: ../../include/notifier.php:786 ../../include/delivery.php:456 -msgid "(no subject)" -msgstr "(без темы)" - -#: ../../include/notifier.php:796 ../../include/delivery.php:467 -#: ../../include/enotify.php:33 -msgid "noreply" -msgstr "без ответа" - -#: ../../include/dba_pdo.php:72 ../../include/dba.php:56 +#: include/dba_pdo.php:72 include/dba.php:55 #, php-format msgid "Cannot locate DNS info for database server '%s'" msgstr "Не могу найти информацию для DNS-сервера базы данных '%s'" -#: ../../include/contact_selectors.php:32 +#: include/contact_selectors.php:32 msgid "Unknown | Not categorised" msgstr "Неизвестно | Не определено" -#: ../../include/contact_selectors.php:33 +#: include/contact_selectors.php:33 msgid "Block immediately" msgstr "Блокировать немедленно" -#: ../../include/contact_selectors.php:34 +#: include/contact_selectors.php:34 msgid "Shady, spammer, self-marketer" msgstr "Тролль, спаммер, рассылает рекламу" -#: ../../include/contact_selectors.php:35 +#: include/contact_selectors.php:35 msgid "Known to me, but no opinion" msgstr "Известные мне, но нет определенного мнения" -#: ../../include/contact_selectors.php:36 +#: include/contact_selectors.php:36 msgid "OK, probably harmless" msgstr "Хорошо, наверное, безвредные" -#: ../../include/contact_selectors.php:37 +#: include/contact_selectors.php:37 msgid "Reputable, has my trust" msgstr "Уважаемые, есть мое доверие" -#: ../../include/contact_selectors.php:60 +#: include/contact_selectors.php:60 msgid "Weekly" msgstr "Еженедельно" -#: ../../include/contact_selectors.php:61 +#: include/contact_selectors.php:61 msgid "Monthly" msgstr "Ежемесячно" -#: ../../include/contact_selectors.php:77 +#: include/contact_selectors.php:77 msgid "OStatus" msgstr "OStatus" -#: ../../include/contact_selectors.php:78 +#: include/contact_selectors.php:78 msgid "RSS/Atom" msgstr "RSS/Atom" -#: ../../include/contact_selectors.php:82 +#: include/contact_selectors.php:81 +msgid "Facebook" +msgstr "Facebook" + +#: include/contact_selectors.php:82 msgid "Zot!" msgstr "Zot!" -#: ../../include/contact_selectors.php:83 +#: include/contact_selectors.php:83 msgid "LinkedIn" msgstr "LinkedIn" -#: ../../include/contact_selectors.php:84 +#: include/contact_selectors.php:84 msgid "XMPP/IM" msgstr "XMPP/IM" -#: ../../include/contact_selectors.php:85 +#: include/contact_selectors.php:85 msgid "MySpace" msgstr "MySpace" -#: ../../include/contact_selectors.php:87 +#: include/contact_selectors.php:87 msgid "Google+" msgstr "Google+" -#: ../../include/contact_selectors.php:88 +#: include/contact_selectors.php:88 msgid "pump.io" msgstr "pump.io" -#: ../../include/contact_selectors.php:89 +#: include/contact_selectors.php:89 msgid "Twitter" msgstr "Twitter" -#: ../../include/contact_selectors.php:90 +#: include/contact_selectors.php:90 msgid "Diaspora Connector" msgstr "" -#: ../../include/contact_selectors.php:91 -msgid "Statusnet" +#: include/contact_selectors.php:91 +msgid "GNU Social" msgstr "" -#: ../../include/contact_selectors.php:92 +#: include/contact_selectors.php:92 msgid "App.net" msgstr "" -#: ../../include/Scrape.php:614 +#: include/contact_selectors.php:103 +msgid "Redmatrix" +msgstr "" + +#: include/Scrape.php:624 msgid " on Last.fm" msgstr "на Last.fm" -#: ../../include/bb2diaspora.php:154 ../../include/event.php:20 +#: include/bb2diaspora.php:154 include/event.php:30 include/event.php:48 msgid "Starts:" msgstr "Начало:" -#: ../../include/bb2diaspora.php:162 ../../include/event.php:30 +#: include/bb2diaspora.php:162 include/event.php:33 include/event.php:54 msgid "Finishes:" msgstr "Окончание:" -#: ../../include/profile_advanced.php:22 -msgid "j F, Y" -msgstr "j F, Y" - -#: ../../include/profile_advanced.php:23 -msgid "j F" -msgstr "j F" - -#: ../../include/profile_advanced.php:30 -msgid "Birthday:" -msgstr "День рождения:" - -#: ../../include/profile_advanced.php:34 -msgid "Age:" -msgstr "Возраст:" - -#: ../../include/profile_advanced.php:43 -#, php-format -msgid "for %1$d %2$s" -msgstr "" - -#: ../../include/profile_advanced.php:52 -msgid "Tags:" -msgstr "Ключевые слова: " - -#: ../../include/profile_advanced.php:56 -msgid "Religion:" -msgstr "Религия:" - -#: ../../include/profile_advanced.php:60 -msgid "Hobbies/Interests:" -msgstr "Хобби / Интересы:" - -#: ../../include/profile_advanced.php:67 -msgid "Contact information and Social Networks:" -msgstr "Информация о контакте и социальных сетях:" - -#: ../../include/profile_advanced.php:69 -msgid "Musical interests:" -msgstr "Музыкальные интересы:" - -#: ../../include/profile_advanced.php:71 -msgid "Books, literature:" -msgstr "Книги, литература:" - -#: ../../include/profile_advanced.php:73 -msgid "Television:" -msgstr "Телевидение:" - -#: ../../include/profile_advanced.php:75 -msgid "Film/dance/culture/entertainment:" -msgstr "Кино / Танцы / Культура / Развлечения:" - -#: ../../include/profile_advanced.php:77 -msgid "Love/Romance:" -msgstr "Любовь / Романтика:" - -#: ../../include/profile_advanced.php:79 -msgid "Work/employment:" -msgstr "Работа / Занятость:" - -#: ../../include/profile_advanced.php:81 -msgid "School/education:" -msgstr "Школа / Образование:" - -#: ../../include/plugin.php:455 ../../include/plugin.php:457 +#: include/plugin.php:522 include/plugin.php:524 msgid "Click here to upgrade." msgstr "Нажмите для обновления." -#: ../../include/plugin.php:463 +#: include/plugin.php:530 msgid "This action exceeds the limits set by your subscription plan." msgstr "Это действие превышает лимиты, установленные вашим тарифным планом." -#: ../../include/plugin.php:468 +#: include/plugin.php:535 msgid "This action is not available under your subscription plan." msgstr "Это действие не доступно в соответствии с вашим планом подписки." -#: ../../include/nav.php:73 +#: include/nav.php:72 msgid "End this session" -msgstr "Конец этой сессии" +msgstr "Завершить эту сессию" -#: ../../include/nav.php:76 ../../include/nav.php:148 -#: ../../view/theme/diabook/theme.php:123 +#: include/nav.php:75 include/nav.php:157 view/theme/diabook/theme.php:123 msgid "Your posts and conversations" -msgstr "Ваши сообщения и беседы" +msgstr "Данные вашей учётной записи" -#: ../../include/nav.php:77 ../../view/theme/diabook/theme.php:124 +#: include/nav.php:76 view/theme/diabook/theme.php:124 msgid "Your profile page" -msgstr "Страница Вашего профиля" +msgstr "Информация о вас" -#: ../../include/nav.php:78 ../../view/theme/diabook/theme.php:126 +#: include/nav.php:77 view/theme/diabook/theme.php:126 msgid "Your photos" msgstr "Ваши фотографии" -#: ../../include/nav.php:79 +#: include/nav.php:78 msgid "Your videos" msgstr "" -#: ../../include/nav.php:80 ../../view/theme/diabook/theme.php:127 +#: include/nav.php:79 view/theme/diabook/theme.php:127 msgid "Your events" msgstr "Ваши события" -#: ../../include/nav.php:81 ../../view/theme/diabook/theme.php:128 +#: include/nav.php:80 view/theme/diabook/theme.php:128 msgid "Personal notes" msgstr "Личные заметки" -#: ../../include/nav.php:81 +#: include/nav.php:80 msgid "Your personal notes" msgstr "" -#: ../../include/nav.php:92 +#: include/nav.php:91 msgid "Sign in" msgstr "Вход" -#: ../../include/nav.php:105 +#: include/nav.php:104 msgid "Home Page" msgstr "Главная страница" -#: ../../include/nav.php:109 +#: include/nav.php:108 msgid "Create an account" msgstr "Создать аккаунт" -#: ../../include/nav.php:114 +#: include/nav.php:113 msgid "Help and documentation" msgstr "Помощь и документация" -#: ../../include/nav.php:117 +#: include/nav.php:116 msgid "Apps" msgstr "Приложения" -#: ../../include/nav.php:117 +#: include/nav.php:116 msgid "Addon applications, utilities, games" msgstr "Дополнительные приложения, утилиты, игры" -#: ../../include/nav.php:119 +#: include/nav.php:118 msgid "Search site content" msgstr "Поиск по сайту" -#: ../../include/nav.php:129 +#: include/nav.php:136 msgid "Conversations on this site" msgstr "Беседы на этом сайте" -#: ../../include/nav.php:131 +#: include/nav.php:138 msgid "Conversations on the network" msgstr "" -#: ../../include/nav.php:133 +#: include/nav.php:142 msgid "Directory" msgstr "Каталог" -#: ../../include/nav.php:133 +#: include/nav.php:142 msgid "People directory" msgstr "Каталог участников" -#: ../../include/nav.php:135 +#: include/nav.php:144 msgid "Information" -msgstr "" +msgstr "Информация" -#: ../../include/nav.php:135 +#: include/nav.php:144 msgid "Information about this friendica instance" msgstr "" -#: ../../include/nav.php:145 +#: include/nav.php:154 msgid "Conversations from your friends" -msgstr "Беседы с друзьями" +msgstr "Посты ваших друзей" -#: ../../include/nav.php:146 +#: include/nav.php:155 msgid "Network Reset" msgstr "Перезагрузка сети" -#: ../../include/nav.php:146 +#: include/nav.php:155 msgid "Load Network page with no filters" msgstr "Загрузить страницу сети без фильтров" -#: ../../include/nav.php:154 +#: include/nav.php:162 msgid "Friend Requests" msgstr "Запросы на добавление в список друзей" -#: ../../include/nav.php:156 +#: include/nav.php:166 msgid "See all notifications" msgstr "Посмотреть все уведомления" -#: ../../include/nav.php:157 +#: include/nav.php:167 msgid "Mark all system notifications seen" msgstr "Отметить все системные уведомления, как прочитанные" -#: ../../include/nav.php:161 +#: include/nav.php:171 msgid "Private mail" msgstr "Личная почта" -#: ../../include/nav.php:162 +#: include/nav.php:172 msgid "Inbox" msgstr "Входящие" -#: ../../include/nav.php:163 +#: include/nav.php:173 msgid "Outbox" msgstr "Исходящие" -#: ../../include/nav.php:167 +#: include/nav.php:177 msgid "Manage" msgstr "Управлять" -#: ../../include/nav.php:167 +#: include/nav.php:177 msgid "Manage other pages" msgstr "Управление другими страницами" -#: ../../include/nav.php:172 +#: include/nav.php:182 msgid "Account settings" msgstr "Настройки аккаунта" -#: ../../include/nav.php:175 +#: include/nav.php:185 msgid "Manage/Edit Profiles" msgstr "Управление/редактирование профилей" -#: ../../include/nav.php:177 +#: include/nav.php:187 msgid "Manage/edit friends and contacts" msgstr "Управление / редактирование друзей и контактов" -#: ../../include/nav.php:184 +#: include/nav.php:194 msgid "Site setup and configuration" -msgstr "Установка и конфигурация сайта" +msgstr "Конфигурация сайта" -#: ../../include/nav.php:188 +#: include/nav.php:198 msgid "Navigation" msgstr "Навигация" -#: ../../include/nav.php:188 +#: include/nav.php:198 msgid "Site map" msgstr "Карта сайта" -#: ../../include/api.php:304 ../../include/api.php:315 -#: ../../include/api.php:416 ../../include/api.php:1063 -#: ../../include/api.php:1065 -msgid "User not found." -msgstr "Пользователь не найден." - -#: ../../include/api.php:771 +#: include/api.php:878 #, php-format msgid "Daily posting limit of %d posts reached. The post was rejected." msgstr "" -#: ../../include/api.php:790 +#: include/api.php:897 #, php-format msgid "Weekly posting limit of %d posts reached. The post was rejected." msgstr "" -#: ../../include/api.php:809 +#: include/api.php:916 #, php-format msgid "Monthly posting limit of %d posts reached. The post was rejected." msgstr "" -#: ../../include/api.php:1272 -msgid "There is no status with this id." -msgstr "Нет статуса с таким id." - -#: ../../include/api.php:1342 -msgid "There is no conversation with this id." -msgstr "" - -#: ../../include/api.php:1614 -msgid "Invalid request." -msgstr "" - -#: ../../include/api.php:1625 -msgid "Invalid item." -msgstr "" - -#: ../../include/api.php:1635 -msgid "Invalid action. " -msgstr "" - -#: ../../include/api.php:1643 -msgid "DB error" -msgstr "" - -#: ../../include/user.php:40 +#: include/user.php:48 msgid "An invitation is required." msgstr "Требуется приглашение." -#: ../../include/user.php:45 +#: include/user.php:53 msgid "Invitation could not be verified." msgstr "Приглашение не может быть проверено." -#: ../../include/user.php:53 +#: include/user.php:61 msgid "Invalid OpenID url" msgstr "Неверный URL OpenID" -#: ../../include/user.php:74 +#: include/user.php:82 msgid "Please enter the required information." msgstr "Пожалуйста, введите необходимую информацию." -#: ../../include/user.php:88 +#: include/user.php:96 msgid "Please use a shorter name." msgstr "Пожалуйста, используйте более короткое имя." -#: ../../include/user.php:90 +#: include/user.php:98 msgid "Name too short." msgstr "Имя слишком короткое." -#: ../../include/user.php:105 +#: include/user.php:113 msgid "That doesn't appear to be your full (First Last) name." msgstr "Кажется, что это ваше неполное (Имя Фамилия) имя." -#: ../../include/user.php:110 +#: include/user.php:118 msgid "Your email domain is not among those allowed on this site." -msgstr "Домен вашего адреса электронной почты не относится к числу разрешенных на этом сайте." +msgstr "" +"Домен вашего адреса электронной почты не относится к числу разрешенных на " +"этом сайте." -#: ../../include/user.php:113 +#: include/user.php:121 msgid "Not a valid email address." msgstr "Неверный адрес электронной почты." -#: ../../include/user.php:126 +#: include/user.php:134 msgid "Cannot use that email." msgstr "Нельзя использовать этот Email." -#: ../../include/user.php:132 -msgid "" -"Your \"nickname\" can only contain \"a-z\", \"0-9\", \"-\", and \"_\", and " -"must also begin with a letter." -msgstr "Ваш \"ник\" может содержать только \"a-z\", \"0-9\", \"-\", и \"_\", а также должен начинаться с буквы." +#: include/user.php:140 +msgid "Your \"nickname\" can only contain \"a-z\", \"0-9\" and \"_\"." +msgstr "" -#: ../../include/user.php:138 ../../include/user.php:236 +#: include/user.php:147 include/user.php:245 msgid "Nickname is already registered. Please choose another." msgstr "Такой ник уже зарегистрирован. Пожалуйста, выберите другой." -#: ../../include/user.php:148 +#: include/user.php:157 msgid "" "Nickname was once registered here and may not be re-used. Please choose " "another." -msgstr "Ник уже зарегистрирован на этом сайте и не может быть изменён. Пожалуйста, выберите другой ник." +msgstr "" +"Ник уже зарегистрирован на этом сайте и не может быть изменён. Пожалуйста, " +"выберите другой ник." -#: ../../include/user.php:164 +#: include/user.php:173 msgid "SERIOUS ERROR: Generation of security keys failed." msgstr "СЕРЬЕЗНАЯ ОШИБКА: генерация ключей безопасности не удалась." -#: ../../include/user.php:222 +#: include/user.php:231 msgid "An error occurred during registration. Please try again." msgstr "Ошибка при регистрации. Пожалуйста, попробуйте еще раз." -#: ../../include/user.php:257 +#: include/user.php:256 view/theme/duepuntozero/config.php:44 +msgid "default" +msgstr "значение по умолчанию" + +#: include/user.php:266 msgid "An error occurred creating your default profile. Please try again." msgstr "Ошибка создания вашего профиля. Пожалуйста, попробуйте еще раз." -#: ../../include/user.php:289 ../../include/user.php:293 -#: ../../include/profile_selectors.php:42 +#: include/user.php:299 include/user.php:303 include/profile_selectors.php:42 msgid "Friends" msgstr "Друзья" -#: ../../include/user.php:377 +#: include/user.php:387 #, php-format msgid "" "\n" @@ -7099,7 +8110,7 @@ msgid "" "\t" msgstr "" -#: ../../include/user.php:381 +#: include/user.php:391 #, php-format msgid "" "\n" @@ -7108,20 +8119,25 @@ msgid "" "\t\t\tLogin Name:\t%1$s\n" "\t\t\tPassword:\t%5$s\n" "\n" -"\t\tYou may change your password from your account \"Settings\" page after logging\n" +"\t\tYou may change your password from your account \"Settings\" page after " +"logging\n" "\t\tin.\n" "\n" -"\t\tPlease take a few moments to review the other account settings on that page.\n" +"\t\tPlease take a few moments to review the other account settings on that " +"page.\n" "\n" "\t\tYou may also wish to add some basic information to your default profile\n" "\t\t(on the \"Profiles\" page) so that other people can easily find you.\n" "\n" "\t\tWe recommend setting your full name, adding a profile photo,\n" -"\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n" -"\t\tperhaps what country you live in; if you do not wish to be more specific\n" +"\t\tadding some profile \"keywords\" (very useful in making new friends) - " +"and\n" +"\t\tperhaps what country you live in; if you do not wish to be more " +"specific\n" "\t\tthan that.\n" "\n" -"\t\tWe fully respect your right to privacy, and none of these items are necessary.\n" +"\t\tWe fully respect your right to privacy, and none of these items are " +"necessary.\n" "\t\tIf you are new and do not know anybody here, they may help\n" "\t\tyou to make some new and interesting friends.\n" "\n" @@ -7129,495 +8145,504 @@ msgid "" "\t\tThank you and welcome to %2$s." msgstr "" -#: ../../include/diaspora.php:703 +#: include/diaspora.php:720 msgid "Sharing notification from Diaspora network" msgstr "Делиться уведомлениями из сети Diaspora" -#: ../../include/diaspora.php:2520 +#: include/diaspora.php:2625 msgid "Attachments:" msgstr "Вложения:" -#: ../../include/items.php:4555 +#: include/delivery.php:533 +msgid "(no subject)" +msgstr "(без темы)" + +#: include/delivery.php:544 include/enotify.php:37 +msgid "noreply" +msgstr "без ответа" + +#: include/items.php:4926 msgid "Do you really want to delete this item?" msgstr "Вы действительно хотите удалить этот элемент?" -#: ../../include/items.php:4778 +#: include/items.php:5201 msgid "Archives" msgstr "Архивы" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Male" msgstr "Мужчина" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Female" msgstr "Женщина" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Currently Male" msgstr "В данный момент мужчина" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Currently Female" msgstr "В настоящее время женщина" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Mostly Male" msgstr "В основном мужчина" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Mostly Female" msgstr "В основном женщина" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Transgender" msgstr "Транссексуал" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Intersex" msgstr "Интерсексуал" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Transsexual" msgstr "Транссексуал" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Hermaphrodite" msgstr "Гермафродит" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Neuter" msgstr "Средний род" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Non-specific" msgstr "Не определен" -#: ../../include/profile_selectors.php:6 +#: include/profile_selectors.php:6 msgid "Other" msgstr "Другой" -#: ../../include/profile_selectors.php:6 -msgid "Undecided" -msgstr "Не решено" - -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Males" msgstr "Мужчины" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Females" msgstr "Женщины" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Gay" msgstr "Гей" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Lesbian" msgstr "Лесбиянка" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "No Preference" msgstr "Без предпочтений" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Bisexual" msgstr "Бисексуал" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Autosexual" msgstr "Автосексуал" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Abstinent" msgstr "Воздержанный" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Virgin" msgstr "Девственница" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Deviant" msgstr "Deviant" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Fetish" msgstr "Фетиш" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Oodles" msgstr "Групповой" -#: ../../include/profile_selectors.php:23 +#: include/profile_selectors.php:23 msgid "Nonsexual" msgstr "Нет интереса к сексу" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Single" msgstr "Без пары" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Lonely" msgstr "Пока никого нет" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Available" msgstr "Доступный" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Unavailable" msgstr "Не ищу никого" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Has crush" msgstr "Имеет ошибку" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Infatuated" msgstr "Влюблён" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Dating" msgstr "Свидания" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Unfaithful" msgstr "Изменяю супругу" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Sex Addict" msgstr "Люблю секс" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Friends/Benefits" msgstr "Друзья / Предпочтения" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Casual" msgstr "Обычный" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Engaged" msgstr "Занят" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Married" msgstr "Женат / Замужем" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Imaginarily married" msgstr "" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Partners" msgstr "Партнеры" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Cohabiting" msgstr "Партнерство" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Common law" msgstr "" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Happy" msgstr "Счастлив/а/" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Not looking" msgstr "Не в поиске" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Swinger" msgstr "Свинг" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Betrayed" msgstr "Преданный" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Separated" msgstr "Разделенный" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Unstable" msgstr "Нестабильный" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Divorced" msgstr "Разведен(а)" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Imaginarily divorced" msgstr "" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Widowed" msgstr "Овдовевший" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Uncertain" msgstr "Неопределенный" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "It's complicated" msgstr "влишком сложно" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Don't care" msgstr "Не беспокоить" -#: ../../include/profile_selectors.php:42 +#: include/profile_selectors.php:42 msgid "Ask me" msgstr "Спросите меня" -#: ../../include/enotify.php:18 +#: include/enotify.php:18 msgid "Friendica Notification" msgstr "Friendica уведомления" -#: ../../include/enotify.php:21 +#: include/enotify.php:21 msgid "Thank You," msgstr "Спасибо," -#: ../../include/enotify.php:23 +#: include/enotify.php:24 #, php-format msgid "%s Administrator" msgstr "%s администратор" -#: ../../include/enotify.php:64 +#: include/enotify.php:26 +#, php-format +msgid "%1$s, %2$s Administrator" +msgstr "" + +#: include/enotify.php:68 #, php-format msgid "%s <!item_type!>" msgstr "%s <!item_type!>" -#: ../../include/enotify.php:68 +#: include/enotify.php:82 #, php-format msgid "[Friendica:Notify] New mail received at %s" msgstr "[Friendica: Оповещение] Новое сообщение, пришедшее на %s" -#: ../../include/enotify.php:70 +#: include/enotify.php:84 #, php-format msgid "%1$s sent you a new private message at %2$s." msgstr "%1$s отправил вам новое личное сообщение на %2$s." -#: ../../include/enotify.php:71 +#: include/enotify.php:85 #, php-format msgid "%1$s sent you %2$s." msgstr "%1$s послал вам %2$s." -#: ../../include/enotify.php:71 +#: include/enotify.php:85 msgid "a private message" msgstr "личное сообщение" -#: ../../include/enotify.php:72 +#: include/enotify.php:86 #, php-format msgid "Please visit %s to view and/or reply to your private messages." -msgstr "Пожалуйста, посетите %s для просмотра и/или ответа на личные сообщения." +msgstr "" +"Пожалуйста, посетите %s для просмотра и/или ответа на личные сообщения." -#: ../../include/enotify.php:124 +#: include/enotify.php:138 #, php-format msgid "%1$s commented on [url=%2$s]a %3$s[/url]" msgstr "%1$s прокомментировал [url=%2$s]a %3$s[/url]" -#: ../../include/enotify.php:131 +#: include/enotify.php:145 #, php-format msgid "%1$s commented on [url=%2$s]%3$s's %4$s[/url]" msgstr "%1$s прокомментировал [url=%2$s]%3$s's %4$s[/url]" -#: ../../include/enotify.php:139 +#: include/enotify.php:153 #, php-format msgid "%1$s commented on [url=%2$s]your %3$s[/url]" msgstr "%1$s прокомментировал [url=%2$s]your %3$s[/url]" -#: ../../include/enotify.php:149 +#: include/enotify.php:163 #, php-format msgid "[Friendica:Notify] Comment to conversation #%1$d by %2$s" msgstr "" -#: ../../include/enotify.php:150 +#: include/enotify.php:164 #, php-format msgid "%s commented on an item/conversation you have been following." msgstr "" -#: ../../include/enotify.php:153 ../../include/enotify.php:168 -#: ../../include/enotify.php:181 ../../include/enotify.php:194 -#: ../../include/enotify.php:212 ../../include/enotify.php:225 +#: include/enotify.php:167 include/enotify.php:182 include/enotify.php:195 +#: include/enotify.php:208 include/enotify.php:226 include/enotify.php:239 #, php-format msgid "Please visit %s to view and/or reply to the conversation." msgstr "" -#: ../../include/enotify.php:160 +#: include/enotify.php:174 #, php-format msgid "[Friendica:Notify] %s posted to your profile wall" msgstr "[Friendica:Оповещение] %s написал на стене вашего профиля" -#: ../../include/enotify.php:162 +#: include/enotify.php:176 #, php-format msgid "%1$s posted to your profile wall at %2$s" msgstr "" -#: ../../include/enotify.php:164 +#: include/enotify.php:178 #, php-format msgid "%1$s posted to [url=%2$s]your wall[/url]" msgstr "" -#: ../../include/enotify.php:175 +#: include/enotify.php:189 #, php-format msgid "[Friendica:Notify] %s tagged you" msgstr "" -#: ../../include/enotify.php:176 +#: include/enotify.php:190 #, php-format msgid "%1$s tagged you at %2$s" msgstr "" -#: ../../include/enotify.php:177 +#: include/enotify.php:191 #, php-format msgid "%1$s [url=%2$s]tagged you[/url]." msgstr "" -#: ../../include/enotify.php:188 +#: include/enotify.php:202 #, php-format msgid "[Friendica:Notify] %s shared a new post" msgstr "" -#: ../../include/enotify.php:189 +#: include/enotify.php:203 #, php-format msgid "%1$s shared a new post at %2$s" msgstr "" -#: ../../include/enotify.php:190 +#: include/enotify.php:204 #, php-format msgid "%1$s [url=%2$s]shared a post[/url]." msgstr "" -#: ../../include/enotify.php:202 +#: include/enotify.php:216 #, php-format msgid "[Friendica:Notify] %1$s poked you" msgstr "" -#: ../../include/enotify.php:203 +#: include/enotify.php:217 #, php-format msgid "%1$s poked you at %2$s" msgstr "" -#: ../../include/enotify.php:204 +#: include/enotify.php:218 #, php-format msgid "%1$s [url=%2$s]poked you[/url]." msgstr "" -#: ../../include/enotify.php:219 +#: include/enotify.php:233 #, php-format msgid "[Friendica:Notify] %s tagged your post" msgstr "" -#: ../../include/enotify.php:220 +#: include/enotify.php:234 #, php-format msgid "%1$s tagged your post at %2$s" msgstr "" -#: ../../include/enotify.php:221 +#: include/enotify.php:235 #, php-format msgid "%1$s tagged [url=%2$s]your post[/url]" msgstr "" -#: ../../include/enotify.php:232 +#: include/enotify.php:246 msgid "[Friendica:Notify] Introduction received" msgstr "[Friendica:Сообщение] получен запрос" -#: ../../include/enotify.php:233 +#: include/enotify.php:247 #, php-format msgid "You've received an introduction from '%1$s' at %2$s" msgstr "" -#: ../../include/enotify.php:234 +#: include/enotify.php:248 #, php-format msgid "You've received [url=%1$s]an introduction[/url] from %2$s." msgstr "" -#: ../../include/enotify.php:237 ../../include/enotify.php:279 +#: include/enotify.php:251 include/enotify.php:293 #, php-format msgid "You may visit their profile at %s" msgstr "Вы можете посмотреть его профиль здесь %s" -#: ../../include/enotify.php:239 +#: include/enotify.php:253 #, php-format msgid "Please visit %s to approve or reject the introduction." msgstr "Посетите %s для подтверждения или отказа запроса." -#: ../../include/enotify.php:247 +#: include/enotify.php:261 msgid "[Friendica:Notify] A new person is sharing with you" msgstr "" -#: ../../include/enotify.php:248 ../../include/enotify.php:249 +#: include/enotify.php:262 include/enotify.php:263 #, php-format msgid "%1$s is sharing with you at %2$s" msgstr "" -#: ../../include/enotify.php:255 +#: include/enotify.php:269 msgid "[Friendica:Notify] You have a new follower" msgstr "" -#: ../../include/enotify.php:256 ../../include/enotify.php:257 +#: include/enotify.php:270 include/enotify.php:271 #, php-format msgid "You have a new follower at %2$s : %1$s" msgstr "" -#: ../../include/enotify.php:270 +#: include/enotify.php:284 msgid "[Friendica:Notify] Friend suggestion received" msgstr "[Friendica: Оповещение] получено предложение дружбы" -#: ../../include/enotify.php:271 +#: include/enotify.php:285 #, php-format msgid "You've received a friend suggestion from '%1$s' at %2$s" msgstr "Вы получили предложение дружбы от '%1$s' на %2$s" -#: ../../include/enotify.php:272 +#: include/enotify.php:286 #, php-format -msgid "" -"You've received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s." +msgid "You've received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s." msgstr "" -#: ../../include/enotify.php:277 +#: include/enotify.php:291 msgid "Name:" msgstr "Имя:" -#: ../../include/enotify.php:278 +#: include/enotify.php:292 msgid "Photo:" msgstr "Фото:" -#: ../../include/enotify.php:281 +#: include/enotify.php:295 #, php-format msgid "Please visit %s to approve or reject the suggestion." msgstr "Пожалуйста, посетите %s для подтверждения, или отказа запроса." -#: ../../include/enotify.php:289 ../../include/enotify.php:302 +#: include/enotify.php:303 include/enotify.php:316 msgid "[Friendica:Notify] Connection accepted" msgstr "" -#: ../../include/enotify.php:290 ../../include/enotify.php:303 +#: include/enotify.php:304 include/enotify.php:317 #, php-format -msgid "'%1$s' has acepted your connection request at %2$s" +msgid "'%1$s' has accepted your connection request at %2$s" msgstr "" -#: ../../include/enotify.php:291 ../../include/enotify.php:304 +#: include/enotify.php:305 include/enotify.php:318 #, php-format msgid "%2$s has accepted your [url=%1$s]connection request[/url]." msgstr "" -#: ../../include/enotify.php:294 +#: include/enotify.php:308 msgid "" -"You are now mutual friends and may exchange status updates, photos, and email\n" +"You are now mutual friends and may exchange status updates, photos, and " +"email\n" "\twithout restriction." msgstr "" -#: ../../include/enotify.php:297 ../../include/enotify.php:311 +#: include/enotify.php:311 include/enotify.php:325 #, php-format msgid "Please visit %s if you wish to make any changes to this relationship." msgstr "" -#: ../../include/enotify.php:307 +#: include/enotify.php:321 #, php-format msgid "" "'%1$s' has chosen to accept you a \"fan\", which restricts some forms of " @@ -7626,266 +8651,261 @@ msgid "" "automatically." msgstr "" -#: ../../include/enotify.php:309 +#: include/enotify.php:323 #, php-format msgid "" "'%1$s' may choose to extend this into a two-way or more permissive " "relationship in the future. " msgstr "" -#: ../../include/enotify.php:322 +#: include/enotify.php:336 msgid "[Friendica System:Notify] registration request" msgstr "" -#: ../../include/enotify.php:323 +#: include/enotify.php:337 #, php-format msgid "You've received a registration request from '%1$s' at %2$s" msgstr "" -#: ../../include/enotify.php:324 +#: include/enotify.php:338 #, php-format msgid "You've received a [url=%1$s]registration request[/url] from %2$s." msgstr "" -#: ../../include/enotify.php:327 +#: include/enotify.php:341 #, php-format msgid "Full Name:\t%1$s\\nSite Location:\t%2$s\\nLogin Name:\t%3$s (%4$s)" msgstr "" -#: ../../include/enotify.php:330 +#: include/enotify.php:344 #, php-format msgid "Please visit %s to approve or reject the request." msgstr "" -#: ../../include/oembed.php:212 +#: include/oembed.php:226 msgid "Embedded content" msgstr "Встроенное содержание" -#: ../../include/oembed.php:221 +#: include/oembed.php:235 msgid "Embedding disabled" msgstr "Встраивание отключено" -#: ../../include/uimport.php:94 +#: include/uimport.php:94 msgid "Error decoding account file" msgstr "Ошибка расшифровки файла аккаунта" -#: ../../include/uimport.php:100 +#: include/uimport.php:100 msgid "Error! No version data in file! This is not a Friendica account file?" -msgstr "Ошибка! Неправильная версия данных в файле! Это не файл аккаунта Friendica?" +msgstr "" +"Ошибка! Неправильная версия данных в файле! Это не файл аккаунта Friendica?" -#: ../../include/uimport.php:116 ../../include/uimport.php:127 +#: include/uimport.php:116 include/uimport.php:127 msgid "Error! Cannot check nickname" msgstr "Ошибка! Невозможно проверить никнейм" -#: ../../include/uimport.php:120 ../../include/uimport.php:131 +#: include/uimport.php:120 include/uimport.php:131 #, php-format msgid "User '%s' already exists on this server!" msgstr "Пользователь '%s' уже существует на этом сервере!" -#: ../../include/uimport.php:153 +#: include/uimport.php:153 msgid "User creation error" msgstr "Ошибка создания пользователя" -#: ../../include/uimport.php:171 +#: include/uimport.php:173 msgid "User profile creation error" msgstr "Ошибка создания профиля пользователя" -#: ../../include/uimport.php:220 +#: include/uimport.php:222 #, php-format msgid "%d contact not imported" msgid_plural "%d contacts not imported" msgstr[0] "%d контакт не импортирован" msgstr[1] "%d контакты не импортированы" msgstr[2] "%d контакты не импортированы" +msgstr[3] "%d контакты не импортированы" -#: ../../include/uimport.php:290 +#: include/uimport.php:292 msgid "Done. You can now login with your username and password" msgstr "Завершено. Теперь вы можете войти с вашим логином и паролем" -#: ../../index.php:428 +#: index.php:442 msgid "toggle mobile" msgstr "мобильная версия" -#: ../../view/theme/cleanzero/config.php:82 -#: ../../view/theme/dispy/config.php:72 ../../view/theme/quattro/config.php:66 -#: ../../view/theme/diabook/config.php:150 ../../view/theme/vier/config.php:55 -#: ../../view/theme/duepuntozero/config.php:61 -msgid "Theme settings" -msgstr "Настройки темы" - -#: ../../view/theme/cleanzero/config.php:83 +#: view/theme/cleanzero/config.php:83 msgid "Set resize level for images in posts and comments (width and height)" -msgstr "Установить уровень изменения размера изображений в постах и комментариях (ширина и высота)" +msgstr "" +"Установить уровень изменения размера изображений в постах и комментариях " +"(ширина и высота)" -#: ../../view/theme/cleanzero/config.php:84 -#: ../../view/theme/dispy/config.php:73 -#: ../../view/theme/diabook/config.php:151 +#: view/theme/cleanzero/config.php:84 view/theme/dispy/config.php:73 +#: view/theme/diabook/config.php:151 msgid "Set font-size for posts and comments" msgstr "Установить шрифт-размер для постов и комментариев" -#: ../../view/theme/cleanzero/config.php:85 +#: view/theme/cleanzero/config.php:85 msgid "Set theme width" msgstr "Установить ширину темы" -#: ../../view/theme/cleanzero/config.php:86 -#: ../../view/theme/quattro/config.php:68 +#: view/theme/cleanzero/config.php:86 view/theme/quattro/config.php:68 msgid "Color scheme" msgstr "Цветовая схема" -#: ../../view/theme/dispy/config.php:74 -#: ../../view/theme/diabook/config.php:152 +#: view/theme/dispy/config.php:74 view/theme/diabook/config.php:152 msgid "Set line-height for posts and comments" msgstr "Установить высоту строки для постов и комментариев" -#: ../../view/theme/dispy/config.php:75 +#: view/theme/dispy/config.php:75 msgid "Set colour scheme" msgstr "Установить цветовую схему" -#: ../../view/theme/quattro/config.php:67 +#: view/theme/quattro/config.php:67 msgid "Alignment" msgstr "Выравнивание" -#: ../../view/theme/quattro/config.php:67 +#: view/theme/quattro/config.php:67 msgid "Left" msgstr "" -#: ../../view/theme/quattro/config.php:67 +#: view/theme/quattro/config.php:67 msgid "Center" msgstr "Центр" -#: ../../view/theme/quattro/config.php:69 +#: view/theme/quattro/config.php:69 msgid "Posts font size" msgstr "Размер шрифта постов" -#: ../../view/theme/quattro/config.php:70 +#: view/theme/quattro/config.php:70 msgid "Textareas font size" msgstr "Размер шрифта текстовых полей" -#: ../../view/theme/diabook/config.php:153 +#: view/theme/diabook/config.php:153 msgid "Set resolution for middle column" msgstr "Установить разрешение для средней колонки" -#: ../../view/theme/diabook/config.php:154 +#: view/theme/diabook/config.php:154 msgid "Set color scheme" msgstr "Установить цветовую схему" -#: ../../view/theme/diabook/config.php:155 +#: view/theme/diabook/config.php:155 msgid "Set zoomfactor for Earth Layer" msgstr "Установить масштаб карты" -#: ../../view/theme/diabook/config.php:156 -#: ../../view/theme/diabook/theme.php:585 +#: view/theme/diabook/config.php:156 view/theme/diabook/theme.php:585 msgid "Set longitude (X) for Earth Layers" msgstr "Установить длину (X) карты" -#: ../../view/theme/diabook/config.php:157 -#: ../../view/theme/diabook/theme.php:586 +#: view/theme/diabook/config.php:157 view/theme/diabook/theme.php:586 msgid "Set latitude (Y) for Earth Layers" msgstr "Установить ширину (Y) карты" -#: ../../view/theme/diabook/config.php:158 -#: ../../view/theme/diabook/theme.php:130 -#: ../../view/theme/diabook/theme.php:544 -#: ../../view/theme/diabook/theme.php:624 +#: view/theme/diabook/config.php:158 view/theme/diabook/theme.php:130 +#: view/theme/diabook/theme.php:544 view/theme/diabook/theme.php:624 +#: view/theme/vier/config.php:111 msgid "Community Pages" msgstr "Страницы сообщества" -#: ../../view/theme/diabook/config.php:159 -#: ../../view/theme/diabook/theme.php:579 -#: ../../view/theme/diabook/theme.php:625 +#: view/theme/diabook/config.php:159 view/theme/diabook/theme.php:579 +#: view/theme/diabook/theme.php:625 msgid "Earth Layers" msgstr "Карта" -#: ../../view/theme/diabook/config.php:160 -#: ../../view/theme/diabook/theme.php:391 -#: ../../view/theme/diabook/theme.php:626 +#: view/theme/diabook/config.php:160 view/theme/diabook/theme.php:391 +#: view/theme/diabook/theme.php:626 view/theme/vier/config.php:112 +#: view/theme/vier/theme.php:156 msgid "Community Profiles" msgstr "Профили сообщества" -#: ../../view/theme/diabook/config.php:161 -#: ../../view/theme/diabook/theme.php:599 -#: ../../view/theme/diabook/theme.php:627 +#: view/theme/diabook/config.php:161 view/theme/diabook/theme.php:599 +#: view/theme/diabook/theme.php:627 view/theme/vier/config.php:113 msgid "Help or @NewHere ?" msgstr "Помощь" -#: ../../view/theme/diabook/config.php:162 -#: ../../view/theme/diabook/theme.php:606 -#: ../../view/theme/diabook/theme.php:628 +#: view/theme/diabook/config.php:162 view/theme/diabook/theme.php:606 +#: view/theme/diabook/theme.php:628 view/theme/vier/config.php:114 +#: view/theme/vier/theme.php:377 msgid "Connect Services" msgstr "Подключить службы" -#: ../../view/theme/diabook/config.php:163 -#: ../../view/theme/diabook/theme.php:523 -#: ../../view/theme/diabook/theme.php:629 +#: view/theme/diabook/config.php:163 view/theme/diabook/theme.php:523 +#: view/theme/diabook/theme.php:629 view/theme/vier/config.php:115 +#: view/theme/vier/theme.php:203 msgid "Find Friends" msgstr "Найти друзей" -#: ../../view/theme/diabook/config.php:164 -#: ../../view/theme/diabook/theme.php:412 -#: ../../view/theme/diabook/theme.php:630 +#: view/theme/diabook/config.php:164 view/theme/diabook/theme.php:412 +#: view/theme/diabook/theme.php:630 view/theme/vier/config.php:116 +#: view/theme/vier/theme.php:185 msgid "Last users" msgstr "Последние пользователи" -#: ../../view/theme/diabook/config.php:165 -#: ../../view/theme/diabook/theme.php:486 -#: ../../view/theme/diabook/theme.php:631 +#: view/theme/diabook/config.php:165 view/theme/diabook/theme.php:486 +#: view/theme/diabook/theme.php:631 msgid "Last photos" msgstr "Последние фото" -#: ../../view/theme/diabook/config.php:166 -#: ../../view/theme/diabook/theme.php:441 -#: ../../view/theme/diabook/theme.php:632 +#: view/theme/diabook/config.php:166 view/theme/diabook/theme.php:441 +#: view/theme/diabook/theme.php:632 msgid "Last likes" msgstr "Последние likes" -#: ../../view/theme/diabook/theme.php:125 +#: view/theme/diabook/theme.php:125 msgid "Your contacts" msgstr "Ваши контакты" -#: ../../view/theme/diabook/theme.php:128 +#: view/theme/diabook/theme.php:128 msgid "Your personal photos" msgstr "Ваши личные фотографии" -#: ../../view/theme/diabook/theme.php:524 +#: view/theme/diabook/theme.php:524 view/theme/vier/theme.php:204 msgid "Local Directory" msgstr "Локальный каталог" -#: ../../view/theme/diabook/theme.php:584 +#: view/theme/diabook/theme.php:584 msgid "Set zoomfactor for Earth Layers" msgstr "Установить масштаб карты" -#: ../../view/theme/diabook/theme.php:622 +#: view/theme/diabook/theme.php:622 msgid "Show/hide boxes at right-hand column:" msgstr "Показать/скрыть блоки в правой колонке:" -#: ../../view/theme/vier/config.php:56 +#: view/theme/vier/config.php:64 +msgid "Comma separated list of helper forums" +msgstr "" + +#: view/theme/vier/config.php:110 msgid "Set style" msgstr "" -#: ../../view/theme/duepuntozero/config.php:45 +#: view/theme/vier/theme.php:295 +msgid "Quick Start" +msgstr "Быстрый запуск" + +#: view/theme/duepuntozero/config.php:45 msgid "greenzero" msgstr "" -#: ../../view/theme/duepuntozero/config.php:46 +#: view/theme/duepuntozero/config.php:46 msgid "purplezero" msgstr "" -#: ../../view/theme/duepuntozero/config.php:47 +#: view/theme/duepuntozero/config.php:47 msgid "easterbunny" msgstr "" -#: ../../view/theme/duepuntozero/config.php:48 +#: view/theme/duepuntozero/config.php:48 msgid "darkzero" msgstr "" -#: ../../view/theme/duepuntozero/config.php:49 +#: view/theme/duepuntozero/config.php:49 msgid "comix" msgstr "" -#: ../../view/theme/duepuntozero/config.php:50 +#: view/theme/duepuntozero/config.php:50 msgid "slackr" msgstr "" -#: ../../view/theme/duepuntozero/config.php:62 +#: view/theme/duepuntozero/config.php:62 msgid "Variations" msgstr "" diff --git a/view/ru/strings.php b/view/ru/strings.php index 09755ca7bb..81600c48f3 100644 --- a/view/ru/strings.php +++ b/view/ru/strings.php @@ -2,13 +2,16 @@ if(! function_exists("string_plural_select_ru")) { function string_plural_select_ru($n){ - return ($n%10==1 && $n%100!=11 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);; + return ; }} ; +$a->strings["Network:"] = "Сеть:"; +$a->strings["Forum"] = "Форум"; $a->strings["%d contact edited."] = array( - 0 => "%d контакт изменён.", - 1 => "%d контакты изменены", - 2 => "%d контакты изменены", + 0 => "", + 1 => "", + 2 => "", + 3 => "", ); $a->strings["Could not access contact record."] = "Не удалось получить доступ к записи контакта."; $a->strings["Could not locate selected profile."] = "Не удалось найти выбранный профиль."; @@ -34,26 +37,12 @@ $a->strings["(Update was successful)"] = "(Обновление было усп $a->strings["(Update was not successful)"] = "(Обновление не удалось)"; $a->strings["Suggest friends"] = "Предложить друзей"; $a->strings["Network type: %s"] = "Сеть: %s"; -$a->strings["%d contact in common"] = array( - 0 => "%d Контакт", - 1 => "%d Контактов", - 2 => "%d Контактов", -); -$a->strings["View all contacts"] = "Показать все контакты"; -$a->strings["Unblock"] = "Разблокировать"; -$a->strings["Block"] = "Заблокировать"; -$a->strings["Toggle Blocked status"] = "Изменить статус блокированности (заблокировать/разблокировать)"; -$a->strings["Unignore"] = "Не игнорировать"; -$a->strings["Ignore"] = "Игнорировать"; -$a->strings["Toggle Ignored status"] = "Изменить статус игнорирования"; -$a->strings["Unarchive"] = "Разархивировать"; -$a->strings["Archive"] = "Архивировать"; -$a->strings["Toggle Archive status"] = "Сменить статус архивации (архивирова/не архивировать)"; -$a->strings["Repair"] = "Восстановить"; -$a->strings["Advanced Contact Settings"] = "Дополнительные Настройки Контакта"; $a->strings["Communications lost with this contact!"] = "Связь с контактом утеряна!"; -$a->strings["Contact Editor"] = "Редактор контакта"; -$a->strings["Submit"] = "Подтвердить"; +$a->strings["Fetch further information for feeds"] = ""; +$a->strings["Disabled"] = "Отключенный"; +$a->strings["Fetch information"] = ""; +$a->strings["Fetch information and keywords"] = ""; +$a->strings["Submit"] = "Добавить"; $a->strings["Profile Visibility"] = "Видимость профиля"; $a->strings["Please choose the profile you would like to display to %s when viewing your profile securely."] = "Пожалуйста, выберите профиль, который вы хотите отображать %s, когда просмотр вашего профиля безопасен."; $a->strings["Contact Information / Notes"] = "Информация о контакте / Заметки"; @@ -67,6 +56,11 @@ $a->strings["Delete contact"] = "Удалить контакт"; $a->strings["Last update:"] = "Последнее обновление: "; $a->strings["Update public posts"] = "Обновить публичные сообщения"; $a->strings["Update now"] = "Обновить сейчас"; +$a->strings["Connect/Follow"] = "Подключиться/Следовать"; +$a->strings["Unblock"] = "Разблокировать"; +$a->strings["Block"] = "Заблокировать"; +$a->strings["Unignore"] = "Не игнорировать"; +$a->strings["Ignore"] = "Игнорировать"; $a->strings["Currently blocked"] = "В настоящее время заблокирован"; $a->strings["Currently ignored"] = "В настоящее время игнорируется"; $a->strings["Currently archived"] = "В данный момент архивирован"; @@ -74,12 +68,12 @@ $a->strings["Hide this contact from others"] = "Скрыть этот конта $a->strings["Replies/likes to your public posts <strong>may</strong> still be visible"] = "Ответы/лайки ваших публичных сообщений <strong>будут</strong> видимы."; $a->strings["Notification for new posts"] = ""; $a->strings["Send a notification of every new post of this contact"] = ""; -$a->strings["Fetch further information for feeds"] = ""; -$a->strings["Disabled"] = ""; -$a->strings["Fetch information"] = ""; -$a->strings["Fetch information and keywords"] = ""; $a->strings["Blacklisted keywords"] = ""; $a->strings["Comma separated list of keywords that should not be converted to hashtags, when \"Fetch information and keywords\" is selected"] = ""; +$a->strings["Profile URL"] = "URL профиля"; +$a->strings["Location:"] = "Откуда:"; +$a->strings["About:"] = "О себе:"; +$a->strings["Tags:"] = "Ключевые слова: "; $a->strings["Suggestions"] = "Предложения"; $a->strings["Suggest potential friends"] = "Предложить потенциального знакомого"; $a->strings["All Contacts"] = "Все контакты"; @@ -94,16 +88,30 @@ $a->strings["Archived"] = "Архивированные"; $a->strings["Only show archived contacts"] = "Показывать только архивные контакты"; $a->strings["Hidden"] = "Скрытые"; $a->strings["Only show hidden contacts"] = "Показывать только скрытые контакты"; -$a->strings["Mutual Friendship"] = "Взаимная дружба"; -$a->strings["is a fan of yours"] = "является вашим поклонником"; -$a->strings["you are a fan of"] = "Вы - поклонник"; -$a->strings["Edit contact"] = "Редактировать контакт"; $a->strings["Contacts"] = "Контакты"; $a->strings["Search your contacts"] = "Поиск ваших контактов"; $a->strings["Finding: "] = "Результат поиска: "; $a->strings["Find"] = "Найти"; $a->strings["Update"] = "Обновление"; +$a->strings["Archive"] = "Архивировать"; +$a->strings["Unarchive"] = "Разархивировать"; $a->strings["Delete"] = "Удалить"; +$a->strings["Status"] = "Посты"; +$a->strings["Status Messages and Posts"] = "Ваши посты"; +$a->strings["Profile"] = "Информация"; +$a->strings["Profile Details"] = "Информация о вас"; +$a->strings["View all contacts"] = "Показать все контакты"; +$a->strings["Common Friends"] = "Общие друзья"; +$a->strings["View all common friends"] = ""; +$a->strings["Repair"] = "Восстановить"; +$a->strings["Advanced Contact Settings"] = "Дополнительные Настройки Контакта"; +$a->strings["Toggle Blocked status"] = "Изменить статус блокированности (заблокировать/разблокировать)"; +$a->strings["Toggle Ignored status"] = "Изменить статус игнорирования"; +$a->strings["Toggle Archive status"] = "Сменить статус архивации (архивирова/не архивировать)"; +$a->strings["Mutual Friendship"] = "Взаимная дружба"; +$a->strings["is a fan of yours"] = "является вашим поклонником"; +$a->strings["you are a fan of"] = "Вы - поклонник"; +$a->strings["Edit contact"] = "Редактировать контакт"; $a->strings["No profile"] = "Нет профиля"; $a->strings["Manage Identities and/or Pages"] = "Управление идентификацией и / или страницами"; $a->strings["Toggle between different identities or community/group pages which share your account details or which you have been granted \"manage\" permissions"] = ""; @@ -112,7 +120,6 @@ $a->strings["Post successful."] = "Успешно добавлено."; $a->strings["Permission denied"] = "Доступ запрещен"; $a->strings["Invalid profile identifier."] = "Недопустимый идентификатор профиля."; $a->strings["Profile Visibility Editor"] = "Редактор видимости профиля"; -$a->strings["Profile"] = "Профиль"; $a->strings["Click on a contact to add or remove."] = "Нажмите на контакт, чтобы добавить или удалить."; $a->strings["Visible To"] = "Видимый для"; $a->strings["All Contacts (with secure profile access)"] = "Все контакты (с безопасным доступом к профилю)"; @@ -137,9 +144,6 @@ $a->strings["Edit your <strong>default</strong> profile to your liking. Review t $a->strings["Profile Keywords"] = "Ключевые слова профиля"; $a->strings["Set some public keywords for your default profile which describe your interests. We may be able to find other people with similar interests and suggest friendships."] = "Установите некоторые публичные ключевые слова для вашего профиля по умолчанию, которые описывают ваши интересы. Мы можем быть в состоянии найти других людей со схожими интересами и предложить дружбу."; $a->strings["Connecting"] = "Подключение"; -$a->strings["Facebook"] = "Facebook"; -$a->strings["Authorise the Facebook Connector if you currently have a Facebook account and we will (optionally) import all your Facebook friends and conversations."] = "Авторизуйте Facebook Connector , если у вас уже есть аккаунт на Facebook, и мы (по желанию) импортируем всех ваших друзей и беседы с Facebook."; -$a->strings["<em>If</em> this is your own personal server, installing the Facebook addon may ease your transition to the free social web."] = "<em>Если</em> это ваш личный сервер, установите дополнение Facebook, это может облегчить ваш переход на свободную социальную сеть."; $a->strings["Importing Emails"] = "Импортирование Email-ов"; $a->strings["Enter your email access information on your Connector Settings page if you wish to import and interact with friends or mailing lists from your email INBOX"] = "Введите информацию о доступе к вашему email на странице настроек вашего коннектора, если вы хотите импортировать, и общаться с друзьями или получать рассылки на ваш ящик электронной почты"; $a->strings["Go to Your Contacts Page"] = "Перейти на страницу ваших контактов"; @@ -164,7 +168,7 @@ $a->strings["Profile Photos"] = "Фотографии профиля"; $a->strings["Image size reduction [%s] failed."] = "Уменьшение размера изображения [%s] не удалось."; $a->strings["Shift-reload the page or clear browser cache if the new photo does not display immediately."] = "Перезагрузите страницу с зажатой клавишей \"Shift\" для того, чтобы увидеть свое новое фото немедленно."; $a->strings["Unable to process image"] = "Не удается обработать изображение"; -$a->strings["Image exceeds size limit of %d"] = "Изображение превышает предельный размер %d"; +$a->strings["Image exceeds size limit of %s"] = ""; $a->strings["Unable to process image."] = "Невозможно обработать фото."; $a->strings["Upload File:"] = "Загрузить файл:"; $a->strings["Select a profile:"] = "Выбрать этот профиль:"; @@ -184,9 +188,28 @@ $a->strings["Tag removed"] = "Ключевое слово удалено"; $a->strings["Remove Item Tag"] = "Удалить ключевое слово"; $a->strings["Select a tag to remove: "] = "Выберите ключевое слово для удаления: "; $a->strings["Remove"] = "Удалить"; +$a->strings["Subscribing to OStatus contacts"] = ""; +$a->strings["No contact provided."] = ""; +$a->strings["Couldn't fetch information for contact."] = ""; +$a->strings["Couldn't fetch friends for contact."] = ""; +$a->strings["Done"] = "Готово"; +$a->strings["success"] = "удачно"; +$a->strings["failed"] = "неудача"; +$a->strings["ignored"] = ""; +$a->strings["Keep this window open until done."] = ""; $a->strings["Save to Folder:"] = "Сохранить в папку:"; $a->strings["- select -"] = "- выбрать -"; $a->strings["Save"] = "Сохранить"; +$a->strings["Submit Request"] = "Отправить запрос"; +$a->strings["You already added this contact."] = ""; +$a->strings["Diaspora support isn't enabled. Contact can't be added."] = ""; +$a->strings["OStatus support is disabled. Contact can't be added."] = ""; +$a->strings["The network type couldn't be detected. Contact can't be added."] = ""; +$a->strings["Please answer the following:"] = "Пожалуйста, ответьте следующее:"; +$a->strings["Does %s know you?"] = "%s знает вас?"; +$a->strings["No"] = "Нет"; +$a->strings["Add a personal note:"] = "Добавить личную заметку:"; +$a->strings["Your Identity Address:"] = "Ваш идентификационный адрес:"; $a->strings["Contact added"] = "Контакт добавлен"; $a->strings["Unable to locate original post."] = "Не удалось найти оригинальный пост."; $a->strings["Empty post discarded."] = "Пустое сообщение отбрасывается."; @@ -207,6 +230,7 @@ $a->strings["Group removed."] = "Группа удалена."; $a->strings["Unable to remove group."] = "Не удается удалить группу."; $a->strings["Group Editor"] = "Редактор групп"; $a->strings["Members"] = "Участники"; +$a->strings["Group is empty"] = "Группа пуста"; $a->strings["You must be logged in to use addons. "] = "Вы должны войти в систему, чтобы использовать аддоны."; $a->strings["Applications"] = "Приложения"; $a->strings["No installed applications."] = "Нет установленных приложений."; @@ -233,6 +257,8 @@ $a->strings["[Name Withheld]"] = "[Имя не разглашается]"; $a->strings["%1\$s has joined %2\$s"] = "%1\$s присоединился %2\$s"; $a->strings["Requested profile is not available."] = "Запрашиваемый профиль недоступен."; $a->strings["Tips for New Members"] = "Советы для новых участников"; +$a->strings["Do you really want to delete this video?"] = ""; +$a->strings["Delete Video"] = "Удалить видео"; $a->strings["No videos selected"] = "Видео не выбрано"; $a->strings["Access to this item is restricted."] = "Доступ к этому пункту ограничен."; $a->strings["View Video"] = "Просмотреть видео"; @@ -243,6 +269,7 @@ $a->strings["%1\$s tagged %2\$s's %3\$s with %4\$s"] = "%1\$s tagged %2\$s's %3\ $a->strings["Friend suggestion sent."] = "Приглашение в друзья отправлено."; $a->strings["Suggest Friends"] = "Предложить друзей"; $a->strings["Suggest a friend for %s"] = "Предложить друга для %s."; +$a->strings["Invalid request."] = "Неверный запрос."; $a->strings["No valid account found."] = "Не найдено действительного аккаунта."; $a->strings["Password reset request issued. Check your email."] = "Запрос на сброс пароля принят. Проверьте вашу электронную почту."; $a->strings["\n\t\tDear %1\$s,\n\t\t\tA request was recently received at \"%2\$s\" to reset your account\n\t\tpassword. In order to confirm this request, please select the verification link\n\t\tbelow or paste it into your web browser address bar.\n\n\t\tIf you did NOT request this change, please DO NOT follow the link\n\t\tprovided and ignore and/or delete this email.\n\n\t\tYour password will not be changed unless we can verify that you\n\t\tissued this request."] = ""; @@ -262,26 +289,16 @@ $a->strings["Forgot your Password?"] = "Забыли пароль?"; $a->strings["Enter your email address and submit to have your password reset. Then check your email for further instructions."] = "Введите адрес электронной почты и подтвердите, что вы хотите сбросить ваш пароль. Затем проверьте свою электронную почту для получения дальнейших инструкций."; $a->strings["Nickname or Email: "] = "Ник или E-mail: "; $a->strings["Reset"] = "Сброс"; -$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s нравится %3\$s от %2\$s "; -$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s не нравится %3\$s от %2\$s "; $a->strings["{0} wants to be your friend"] = "{0} хочет стать Вашим другом"; $a->strings["{0} sent you a message"] = "{0} отправил Вам сообщение"; $a->strings["{0} requested registration"] = "{0} требуемая регистрация"; -$a->strings["{0} commented %s's post"] = "{0} прокомментировал сообщение от %s"; -$a->strings["{0} liked %s's post"] = "{0} нравится сообщение от %s"; -$a->strings["{0} disliked %s's post"] = "{0} не нравится сообщение от %s"; -$a->strings["{0} is now friends with %s"] = "{0} теперь друзья с %s"; -$a->strings["{0} posted"] = "{0} опубликовано"; -$a->strings["{0} tagged %s's post with #%s"] = "{0} пометил сообщение %s с #%s"; -$a->strings["{0} mentioned you in a post"] = "{0} упоменул Вас в сообщение"; $a->strings["No contacts."] = "Нет контактов."; -$a->strings["View Contacts"] = "Просмотр контактов"; $a->strings["Invalid request identifier."] = "Неверный идентификатор запроса."; $a->strings["Discard"] = "Отказаться"; $a->strings["System"] = "Система"; -$a->strings["Network"] = "Сеть"; +$a->strings["Network"] = "Новости"; $a->strings["Personal"] = "Персонал"; -$a->strings["Home"] = "Главная"; +$a->strings["Home"] = "Мой профиль"; $a->strings["Introductions"] = "Запросы"; $a->strings["Show Ignored Requests"] = "Показать проигнорированные запросы"; $a->strings["Hide Ignored Requests"] = "Скрыть проигнорированные запросы"; @@ -294,12 +311,14 @@ $a->strings["Approve"] = "Одобрить"; $a->strings["Claims to be known to you: "] = "Утверждения, о которых должно быть вам известно: "; $a->strings["yes"] = "да"; $a->strings["no"] = "нет"; -$a->strings["Approve as: "] = "Утвердить как: "; +$a->strings["Shall your connection be bidirectional or not? \"Friend\" implies that you allow to read and you subscribe to their posts. \"Fan/Admirer\" means that you allow to read but you do not want to read theirs. Approve as: "] = ""; +$a->strings["Shall your connection be bidirectional or not? \"Friend\" implies that you allow to read and you subscribe to their posts. \"Sharer\" means that you allow to read but you do not want to read theirs. Approve as: "] = ""; $a->strings["Friend"] = "Друг"; $a->strings["Sharer"] = "Участник"; $a->strings["Fan/Admirer"] = "Фанат / Поклонник"; $a->strings["Friend/Connect Request"] = "Запрос в друзья / на подключение"; $a->strings["New Follower"] = "Новый фолловер"; +$a->strings["Gender:"] = "Пол:"; $a->strings["No introductions."] = "Запросов нет."; $a->strings["Notifications"] = "Уведомления"; $a->strings["%s liked %s's post"] = "%s нравится %s сообшение"; @@ -348,30 +367,31 @@ $a->strings["Upload photo"] = "Загрузить фото"; $a->strings["Insert web link"] = "Вставить веб-ссылку"; $a->strings["Please wait"] = "Пожалуйста, подождите"; $a->strings["No messages."] = "Нет сообщений."; +$a->strings["Message not available."] = "Сообщение не доступно."; +$a->strings["Delete message"] = "Удалить сообщение"; +$a->strings["Delete conversation"] = "Удалить историю общения"; +$a->strings["No secure communications available. You <strong>may</strong> be able to respond from the sender's profile page."] = "Невозможно защищённое соединение. Вы <strong>имеете</strong> возможность ответить со страницы профиля отправителя."; +$a->strings["Send Reply"] = "Отправить ответ"; $a->strings["Unknown sender - %s"] = "Неизвестный отправитель - %s"; $a->strings["You and %s"] = "Вы и %s"; $a->strings["%s and You"] = "%s и Вы"; -$a->strings["Delete conversation"] = "Удалить историю общения"; $a->strings["D, d M Y - g:i A"] = "D, d M Y - g:i A"; $a->strings["%d message"] = array( 0 => "%d сообщение", 1 => "%d сообщений", 2 => "%d сообщений", + 3 => "%d сообщений", ); -$a->strings["Message not available."] = "Сообщение не доступно."; -$a->strings["Delete message"] = "Удалить сообщение"; -$a->strings["No secure communications available. You <strong>may</strong> be able to respond from the sender's profile page."] = "Невозможно защищённое соединение. Вы <strong>имеете</strong> возможность ответить со страницы профиля отправителя."; -$a->strings["Send Reply"] = "Отправить ответ"; $a->strings["[Embedded content - reload page to view]"] = "[Встроенное содержание - перезагрузите страницу для просмотра]"; $a->strings["Contact settings applied."] = "Установки контакта приняты."; $a->strings["Contact update failed."] = "Обновление контакта неудачное."; -$a->strings["Repair Contact Settings"] = "Восстановить установки контакта"; $a->strings["<strong>WARNING: This is highly advanced</strong> and if you enter incorrect information your communications with this contact may stop working."] = "<strong>ВНИМАНИЕ: Это крайне важно!</strong> Если вы введете неверную информацию, ваша связь с этим контактом перестанет работать."; $a->strings["Please use your browser 'Back' button <strong>now</strong> if you are uncertain what to do on this page."] = "Пожалуйста, нажмите клавишу вашего браузера 'Back' или 'Назад' <strong>сейчас</strong>, если вы не уверены, что делаете на этой странице."; -$a->strings["Return to contact editor"] = "Возврат к редактору контакта"; $a->strings["No mirroring"] = ""; $a->strings["Mirror as forwarded posting"] = ""; $a->strings["Mirror as my own posting"] = ""; +$a->strings["Return to contact editor"] = "Возврат к редактору контакта"; +$a->strings["Refetch contact data"] = ""; $a->strings["Name"] = "Имя"; $a->strings["Account Nickname"] = "Ник аккаунта"; $a->strings["@Tagname - overrides Name/Nickname"] = ""; @@ -387,9 +407,12 @@ $a->strings["Mark this contact as remote_self, this will cause friendica to repo $a->strings["Login"] = "Вход"; $a->strings["The post was created"] = ""; $a->strings["Access denied."] = "Доступ запрещен."; -$a->strings["People Search"] = "Поиск людей"; +$a->strings["Connect"] = "Подключить"; +$a->strings["View Profile"] = "Просмотреть профиль"; +$a->strings["People Search - %s"] = ""; $a->strings["No matches"] = "Нет соответствий"; $a->strings["Photos"] = "Фото"; +$a->strings["Contact Photos"] = "Фотографии контакта"; $a->strings["Files"] = "Файлы"; $a->strings["Contacts who are not members of a group"] = "Контакты, которые не являются членами группы"; $a->strings["Theme settings updated."] = "Настройки темы обновлены."; @@ -397,14 +420,28 @@ $a->strings["Site"] = "Сайт"; $a->strings["Users"] = "Пользователи"; $a->strings["Plugins"] = "Плагины"; $a->strings["Themes"] = "Темы"; +$a->strings["Additional features"] = "Дополнительные возможности"; $a->strings["DB updates"] = "Обновление БД"; +$a->strings["Inspect Queue"] = ""; +$a->strings["Federation Statistics"] = ""; $a->strings["Logs"] = "Журналы"; +$a->strings["View Logs"] = "Просмотр логов"; $a->strings["probe address"] = ""; $a->strings["check webfinger"] = ""; $a->strings["Admin"] = "Администратор"; $a->strings["Plugin Features"] = "Возможности плагина"; -$a->strings["diagnostics"] = ""; +$a->strings["diagnostics"] = "Диагностика"; $a->strings["User registrations waiting for confirmation"] = "Регистрации пользователей, ожидающие подтверждения"; +$a->strings["This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."] = ""; +$a->strings["The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here."] = ""; +$a->strings["Administration"] = "Администрация"; +$a->strings["Currently this node is aware of %d nodes from the following platforms:"] = ""; +$a->strings["ID"] = ""; +$a->strings["Recipient Name"] = ""; +$a->strings["Recipient Profile"] = ""; +$a->strings["Created"] = ""; +$a->strings["Last Tried"] = ""; +$a->strings["This page lists the content of the queue for outgoing postings. These are postings the initial delivery failed for. They will be resend later and eventually deleted if the delivery fails permanently."] = ""; $a->strings["Normal Account"] = "Обычный аккаунт"; $a->strings["Soapbox Account"] = "Аккаунт Витрина"; $a->strings["Community/Celebrity Account"] = "Аккаунт Сообщество / Знаменитость"; @@ -412,13 +449,13 @@ $a->strings["Automatic Friend Account"] = "\"Автоматический дру $a->strings["Blog Account"] = "Аккаунт блога"; $a->strings["Private Forum"] = "Личный форум"; $a->strings["Message queues"] = "Очереди сообщений"; -$a->strings["Administration"] = "Администрация"; $a->strings["Summary"] = "Резюме"; $a->strings["Registered users"] = "Зарегистрированные пользователи"; $a->strings["Pending registrations"] = "Ожидающие регистрации"; $a->strings["Version"] = "Версия"; $a->strings["Active plugins"] = "Активные плагины"; $a->strings["Can not parse base url. Must have at least <scheme>://<domain>"] = "Невозможно определить базовый URL. Он должен иметь следующий вид - <scheme>://<domain>"; +$a->strings["RINO2 needs mcrypt php extension to work."] = "Для функционирования RINO2 необходим пакет php5-mcrypt"; $a->strings["Site settings updated."] = "Установки сайта обновлены."; $a->strings["No special theme for mobile devices"] = "Нет специальной темы для мобильных устройств"; $a->strings["No community page"] = ""; @@ -429,6 +466,12 @@ $a->strings["Frequently"] = "Часто"; $a->strings["Hourly"] = "Раз в час"; $a->strings["Twice daily"] = "Два раза в день"; $a->strings["Daily"] = "Ежедневно"; +$a->strings["Users, Global Contacts"] = ""; +$a->strings["Users, Global Contacts/fallback"] = ""; +$a->strings["One month"] = "Один месяц"; +$a->strings["Three months"] = "Три месяца"; +$a->strings["Half a year"] = "Пол года"; +$a->strings["One year"] = "Один год"; $a->strings["Multi user instance"] = "Многопользовательский вид"; $a->strings["Closed"] = "Закрыто"; $a->strings["Requires approval"] = "Требуется подтверждение"; @@ -441,16 +484,20 @@ $a->strings["Registration"] = "Регистрация"; $a->strings["File upload"] = "Загрузка файлов"; $a->strings["Policies"] = "Политики"; $a->strings["Advanced"] = "Расширенный"; +$a->strings["Auto Discovered Contact Directory"] = ""; $a->strings["Performance"] = "Производительность"; $a->strings["Relocate - WARNING: advanced function. Could make this server unreachable."] = "Переместить - ПРЕДУПРЕЖДЕНИЕ: расширеная функция. Может сделать этот сервер недоступным."; $a->strings["Site name"] = "Название сайта"; -$a->strings["Host name"] = ""; -$a->strings["Sender Email"] = ""; +$a->strings["Host name"] = "Имя хоста"; +$a->strings["Sender Email"] = "Системный Email"; +$a->strings["The email address your server shall use to send notification emails from."] = "Адрес с которого будут приходить письма пользователям."; $a->strings["Banner/Logo"] = "Баннер/Логотип"; $a->strings["Shortcut icon"] = ""; +$a->strings["Link to an icon that will be used for browsers."] = ""; $a->strings["Touch icon"] = ""; +$a->strings["Link to an icon that will be used for tablets and mobiles."] = ""; $a->strings["Additional Info"] = "Дополнительная информация"; -$a->strings["For public servers: you can add additional information here that will be listed at dir.friendica.com/siteinfo."] = "Для публичных серверов: вы можете добавить дополнительную информацию, которая будет перечислена в dir.friendica.com/siteinfo."; +$a->strings["For public servers: you can add additional information here that will be listed at %s/siteinfo."] = ""; $a->strings["System language"] = "Системный язык"; $a->strings["System theme"] = "Системная тема"; $a->strings["Default system theme - may be over-ridden by user profiles - <a href='#' id='cnftheme'>change theme settings</a>"] = "Тема системы по умолчанию - может быть переопределена пользователем - <a href='#' id='cnftheme'>изменить настройки темы</a>"; @@ -458,7 +505,7 @@ $a->strings["Mobile system theme"] = "Мобильная тема системы $a->strings["Theme for mobile devices"] = "Тема для мобильных устройств"; $a->strings["SSL link policy"] = "Политика SSL"; $a->strings["Determines whether generated links should be forced to use SSL"] = "Ссылки должны быть вынуждены использовать SSL"; -$a->strings["Force SSL"] = ""; +$a->strings["Force SSL"] = "SSL принудительно"; $a->strings["Force all Non-SSL requests to SSL - Attention: on some systems it could lead to endless loops."] = ""; $a->strings["Old style 'Share'"] = "Старый стиль 'Share'"; $a->strings["Deactivates the bbcode element 'share' for repeating items."] = "Отключение BBCode элемента 'share' для повторяющихся элементов."; @@ -487,8 +534,8 @@ $a->strings["Block public"] = "Блокировать общественный $a->strings["Check to block public access to all otherwise public personal pages on this site unless you are currently logged in."] = "Отметьте, чтобы заблокировать публичный доступ ко всем иным публичным персональным страницам на этом сайте, если вы не вошли на сайт."; $a->strings["Force publish"] = "Принудительная публикация"; $a->strings["Check to force all profiles on this site to be listed in the site directory."] = "Отметьте, чтобы принудительно заставить все профили на этом сайте, быть перечислеными в каталоге сайта."; -$a->strings["Global directory update URL"] = "URL обновления глобального каталога"; -$a->strings["URL to update the global directory. If this is not set, the global directory is completely unavailable to the application."] = "URL для обновления глобального каталога. Если он не установлен, глобальный каталог полностью недоступен для приложения."; +$a->strings["Global directory URL"] = ""; +$a->strings["URL to the global directory. If this is not set, the global directory is completely unavailable to the application."] = ""; $a->strings["Allow threaded items"] = "Разрешить темы в обсуждении"; $a->strings["Allow infinite level threading for items on this site."] = "Разрешить бесконечный уровень для тем на этом сайте."; $a->strings["Private posts by default for new users"] = "Частные сообщения по умолчанию для новых пользователей"; @@ -517,6 +564,8 @@ $a->strings["Enable OStatus support"] = "Включить поддержку OSt $a->strings["Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed."] = ""; $a->strings["OStatus conversation completion interval"] = ""; $a->strings["How often shall the poller check for new entries in OStatus conversations? This can be a very ressource task."] = "Как часто процессы должны проверять наличие новых записей в OStatus разговорах? Это может быть очень ресурсоёмкой задачей."; +$a->strings["OStatus support can only be enabled if threading is enabled."] = ""; +$a->strings["Diaspora support can't be enabled because Friendica was installed into a sub directory."] = ""; $a->strings["Enable Diaspora support"] = "Включить поддержку Diaspora"; $a->strings["Provide built-in Diaspora network compatibility."] = "Обеспечить встроенную поддержку сети Diaspora."; $a->strings["Only allow Friendica contacts"] = "Позвольть только Friendica контакты"; @@ -533,6 +582,24 @@ $a->strings["Poll interval"] = "Интервал опроса"; $a->strings["Delay background polling processes by this many seconds to reduce system load. If 0, use delivery interval."] = "Установить задержку фоновых процессов опросов путем ограничения количества секунд, чтобы уменьшить нагрузку на систему. Если 0, используется интервал доставки."; $a->strings["Maximum Load Average"] = "Средняя максимальная нагрузка"; $a->strings["Maximum system load before delivery and poll processes are deferred - default 50."] = "Максимальная нагрузка на систему перед приостановкой процессов доставки и опросов - по умолчанию 50."; +$a->strings["Maximum Load Average (Frontend)"] = ""; +$a->strings["Maximum system load before the frontend quits service - default 50."] = ""; +$a->strings["Maximum table size for optimization"] = ""; +$a->strings["Maximum table size (in MB) for the automatic optimization - default 100 MB. Enter -1 to disable it."] = ""; +$a->strings["Minimum level of fragmentation"] = ""; +$a->strings["Minimum fragmenation level to start the automatic optimization - default value is 30%."] = ""; +$a->strings["Periodical check of global contacts"] = ""; +$a->strings["If enabled, the global contacts are checked periodically for missing or outdated data and the vitality of the contacts and servers."] = ""; +$a->strings["Days between requery"] = ""; +$a->strings["Number of days after which a server is requeried for his contacts."] = ""; +$a->strings["Discover contacts from other servers"] = ""; +$a->strings["Periodically query other servers for contacts. You can choose between 'users': the users on the remote system, 'Global Contacts': active contacts that are known on the system. The fallback is meant for Redmatrix servers and older friendica servers, where global contacts weren't available. The fallback increases the server load, so the recommened setting is 'Users, Global Contacts'."] = ""; +$a->strings["Timeframe for fetching global contacts"] = ""; +$a->strings["When the discovery is activated, this value defines the timeframe for the activity of the global contacts that are fetched from other servers."] = ""; +$a->strings["Search the local directory"] = ""; +$a->strings["Search the local directory instead of the global directory. When searching locally, every search will be executed on the global directory in the background. This improves the search results when the search is repeated."] = ""; +$a->strings["Publish server information"] = ""; +$a->strings["If enabled, general server and usage data will be published. The data contains the name and version of the server, number of users with public profiles, number of posts and the activated protocols and connectors. See <a href='http://the-federation.info/'>the-federation.info</a> for details."] = ""; $a->strings["Use MySQL full text engine"] = "Использовать систему полнотексного поиска MySQL"; $a->strings["Activates the full text engine. Speeds up search - but can only search for four and more characters."] = "Активизирует систему полнотексного поиска. Ускоряет поиск - но может искать только при указании четырех и более символов."; $a->strings["Suppress Language"] = ""; @@ -540,13 +607,17 @@ $a->strings["Suppress language information in meta information about a posting." $a->strings["Suppress Tags"] = ""; $a->strings["Suppress showing a list of hashtags at the end of the posting."] = ""; $a->strings["Path to item cache"] = "Путь к элементам кэша"; +$a->strings["The item caches buffers generated bbcode and external images."] = ""; $a->strings["Cache duration in seconds"] = "Время жизни кэша в секундах"; $a->strings["How long should the cache files be hold? Default value is 86400 seconds (One day). To disable the item cache, set the value to -1."] = ""; $a->strings["Maximum numbers of comments per post"] = ""; $a->strings["How much comments should be shown for each post? Default value is 100."] = ""; $a->strings["Path for lock file"] = "Путь к файлу блокировки"; +$a->strings["The lock file is used to avoid multiple pollers at one time. Only define a folder here."] = ""; $a->strings["Temp path"] = "Временная папка"; +$a->strings["If you have a restricted system where the webserver can't access the system temp path, enter another path here."] = ""; $a->strings["Base path to installation"] = "Путь для установки"; +$a->strings["If the system cannot detect the correct path to your installation, enter the correct path here. This setting should only be set if you are using a restricted system and symbolic links to your webroot."] = ""; $a->strings["Disable picture proxy"] = ""; $a->strings["The picture proxy increases performance and privacy. It shouldn't be used on systems with very low bandwith."] = ""; $a->strings["Enable old style pager"] = ""; @@ -554,6 +625,11 @@ $a->strings["The old style pager has page numbers but slows down massively the p $a->strings["Only search in tags"] = ""; $a->strings["On large systems the text search can slow down the system extremely."] = ""; $a->strings["New base url"] = "Новый базовый url"; +$a->strings["Change base url for this server. Sends relocate message to all DFRN contacts of all users."] = ""; +$a->strings["RINO Encryption"] = "RINO шифрование"; +$a->strings["Encryption layer between nodes."] = "Слой шифрования между узлами."; +$a->strings["Embedly API key"] = ""; +$a->strings["<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for web pages. This is an optional parameter."] = ""; $a->strings["Update has been marked successful"] = "Обновление было успешно отмечено"; $a->strings["Database structure update %s was successfully applied."] = ""; $a->strings["Executing of database structure update %s failed with error: %s"] = ""; @@ -574,11 +650,13 @@ $a->strings["%s user blocked/unblocked"] = array( 0 => "%s пользователь заблокирован/разблокирован", 1 => "%s пользователей заблокировано/разблокировано", 2 => "%s пользователей заблокировано/разблокировано", + 3 => "%s пользователей заблокировано/разблокировано", ); $a->strings["%s user deleted"] = array( 0 => "%s человек удален", 1 => "%s чел. удалено", 2 => "%s чел. удалено", + 3 => "%s чел. удалено", ); $a->strings["User '%s' deleted"] = "Пользователь '%s' удален"; $a->strings["User '%s' unblocked"] = "Пользователь '%s' разблокирован"; @@ -612,8 +690,12 @@ $a->strings["Enable"] = "Включить"; $a->strings["Toggle"] = "Переключить"; $a->strings["Author: "] = "Автор:"; $a->strings["Maintainer: "] = "Программа обслуживания: "; +$a->strings["Reload active plugins"] = ""; +$a->strings["There are currently no plugins available on your node. You can find the official plugin repository at %1\$s and might find other interesting plugins in the open plugin registry at %2\$s"] = ""; $a->strings["No themes found."] = "Темы не найдены."; $a->strings["Screenshot"] = "Скриншот"; +$a->strings["Reload active themes"] = ""; +$a->strings["No themes found on the system. They should be paced in %1\$s"] = ""; $a->strings["[Experimental]"] = "[экспериментально]"; $a->strings["[Unsupported]"] = "[Неподдерживаемое]"; $a->strings["Log settings updated."] = "Настройки журнала обновлены."; @@ -622,18 +704,19 @@ $a->strings["Enable Debugging"] = "Включить отладку"; $a->strings["Log file"] = "Лог-файл"; $a->strings["Must be writable by web server. Relative to your Friendica top-level directory."] = "Должно быть доступно для записи в веб-сервере. Относительно вашего Friendica каталога верхнего уровня."; $a->strings["Log level"] = "Уровень лога"; -$a->strings["Close"] = "Закрыть"; -$a->strings["FTP Host"] = "FTP хост"; -$a->strings["FTP Path"] = "Путь FTP"; -$a->strings["FTP User"] = "FTP пользователь"; -$a->strings["FTP Password"] = "FTP пароль"; -$a->strings["Search Results For:"] = "Результаты поиска для:"; +$a->strings["PHP logging"] = "PHP логирование"; +$a->strings["To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."] = ""; +$a->strings["Off"] = "Выкл."; +$a->strings["On"] = "Вкл."; +$a->strings["Lock feature %s"] = ""; +$a->strings["Manage Additional Features"] = ""; +$a->strings["Search Results For: %s"] = ""; $a->strings["Remove term"] = "Удалить элемент"; $a->strings["Saved Searches"] = "запомненные поиски"; $a->strings["add"] = "добавить"; -$a->strings["Commented Order"] = "Прокомментированный запрос"; +$a->strings["Commented Order"] = "Последние комментарии"; $a->strings["Sort by Comment Date"] = "Сортировать по дате комментария"; -$a->strings["Posted Order"] = "Отправленный запрос"; +$a->strings["Posted Order"] = "Лента записей"; $a->strings["Sort by Post Date"] = "Сортировать по дате отправки"; $a->strings["Posts that mention or involve you"] = ""; $a->strings["New"] = "Новый"; @@ -646,36 +729,77 @@ $a->strings["Warning: This group contains %s member from an insecure network."] 0 => "Внимание: Эта группа содержит %s участника с незащищенной сети.", 1 => "Внимание: Эта группа содержит %s участников с незащищенной сети.", 2 => "Внимание: Эта группа содержит %s участников с незащищенной сети.", + 3 => "Внимание: Эта группа содержит %s участников с незащищенной сети.", ); $a->strings["Private messages to this group are at risk of public disclosure."] = "Личные сообщения к этой группе находятся под угрозой обнародования."; $a->strings["No such group"] = "Нет такой группы"; -$a->strings["Group is empty"] = "Группа пуста"; -$a->strings["Group: "] = "Группа: "; -$a->strings["Contact: "] = "Контакт: "; +$a->strings["Group: %s"] = "Группа: %s"; $a->strings["Private messages to this person are at risk of public disclosure."] = "Личные сообщения этому человеку находятся под угрозой обнародования."; $a->strings["Invalid contact."] = "Недопустимый контакт."; -$a->strings["Friends of %s"] = "%s Друзья"; $a->strings["No friends to display."] = "Нет друзей."; +$a->strings["Event can not end before it has started."] = ""; $a->strings["Event title and start time are required."] = "Название мероприятия и время начала обязательны для заполнения."; +$a->strings["Sun"] = "Вс"; +$a->strings["Mon"] = "Пн"; +$a->strings["Tue"] = "Вт"; +$a->strings["Wed"] = "Ср"; +$a->strings["Thu"] = "Чт"; +$a->strings["Fri"] = "Пт"; +$a->strings["Sat"] = "Сб"; +$a->strings["Sunday"] = "Воскресенье"; +$a->strings["Monday"] = "Понедельник"; +$a->strings["Tuesday"] = "Вторник"; +$a->strings["Wednesday"] = "Среда"; +$a->strings["Thursday"] = "Четверг"; +$a->strings["Friday"] = "Пятница"; +$a->strings["Saturday"] = "Суббота"; +$a->strings["Jan"] = "Янв"; +$a->strings["Feb"] = "Фев"; +$a->strings["Mar"] = "Мрт"; +$a->strings["Apr"] = "Апр"; +$a->strings["May"] = "Май"; +$a->strings["Jun"] = "Июн"; +$a->strings["Jul"] = "Июл"; +$a->strings["Aug"] = "Авг"; +$a->strings["Sept"] = "Сен"; +$a->strings["Oct"] = "Окт"; +$a->strings["Nov"] = "Нбр"; +$a->strings["Dec"] = "Дек"; +$a->strings["January"] = "Январь"; +$a->strings["February"] = "Февраль"; +$a->strings["March"] = "Март"; +$a->strings["April"] = "Апрель"; +$a->strings["June"] = "Июнь"; +$a->strings["July"] = "Июль"; +$a->strings["August"] = "Август"; +$a->strings["September"] = "Сентябрь"; +$a->strings["October"] = "Октябрь"; +$a->strings["November"] = "Ноябрь"; +$a->strings["December"] = "Декабрь"; +$a->strings["today"] = "сегодня"; +$a->strings["month"] = "мес."; +$a->strings["week"] = "неделя"; +$a->strings["day"] = "день"; $a->strings["l, F j"] = "l, j F"; $a->strings["Edit event"] = "Редактировать мероприятие"; -$a->strings["link to source"] = "ссылка на источник"; +$a->strings["link to source"] = "ссылка на сообщение"; $a->strings["Events"] = "Мероприятия"; $a->strings["Create New Event"] = "Создать новое мероприятие"; $a->strings["Previous"] = "Назад"; $a->strings["Next"] = "Далее"; -$a->strings["hour:minute"] = "час:минута"; $a->strings["Event details"] = "Сведения о мероприятии"; -$a->strings["Format is %s %s. Starting date and Title are required."] = "Формат %s %s. Необхлдима дата старта и заголовок."; +$a->strings["Starting date and Title are required."] = ""; $a->strings["Event Starts:"] = "Начало мероприятия:"; $a->strings["Required"] = "Требуется"; $a->strings["Finish date/time is not known or not relevant"] = "Дата/время окончания не известны, или не указаны"; $a->strings["Event Finishes:"] = "Окончание мероприятия:"; $a->strings["Adjust for viewer timezone"] = "Настройка часового пояса"; $a->strings["Description:"] = "Описание:"; -$a->strings["Location:"] = "Откуда:"; $a->strings["Title:"] = "Титул:"; $a->strings["Share this event"] = "Поделитесь этим мероприятием"; +$a->strings["Preview"] = "Предварительный просмотр"; +$a->strings["Credits"] = ""; +$a->strings["Friendica is a community project, that would not be possible without the help of many people. Here is a list of those who have contributed to the code or the translation of Friendica. Thank you all!"] = ""; $a->strings["Select"] = "Выберите"; $a->strings["View %s's profile @ %s"] = "Просмотреть профиль %s [@ %s]"; $a->strings["%s from %s"] = "%s с %s"; @@ -684,11 +808,13 @@ $a->strings["%d comment"] = array( 0 => "%d комментарий", 1 => "%d комментариев", 2 => "%d комментариев", + 3 => "%d комментариев", ); $a->strings["comment"] = array( 0 => "", 1 => "", 2 => "комментарий", + 3 => "комментарий", ); $a->strings["show more"] = "показать больше"; $a->strings["Private Message"] = "Личное сообщение"; @@ -699,7 +825,7 @@ $a->strings["dislike"] = "не нравитса"; $a->strings["Share this"] = "Поделитесь этим"; $a->strings["share"] = "делиться"; $a->strings["This is you"] = "Это вы"; -$a->strings["Comment"] = "Комментарий"; +$a->strings["Comment"] = "Оставить комментарий"; $a->strings["Bold"] = "Жирный"; $a->strings["Italic"] = "Kурсивный"; $a->strings["Underline"] = "Подчеркнутый"; @@ -708,7 +834,6 @@ $a->strings["Code"] = "Код"; $a->strings["Image"] = "Изображение / Фото"; $a->strings["Link"] = "Ссылка"; $a->strings["Video"] = "Видео"; -$a->strings["Preview"] = "предварительный просмотр"; $a->strings["Edit"] = "Редактировать"; $a->strings["add star"] = "пометить"; $a->strings["remove star"] = "убрать метку"; @@ -728,6 +853,7 @@ $a->strings["Could not create table."] = "Не удалось создать т $a->strings["Your Friendica site database has been installed."] = "База данных сайта установлена."; $a->strings["You may need to import the file \"database.sql\" manually using phpmyadmin or mysql."] = "Вам может понадобиться импортировать файл \"database.sql\" вручную с помощью PhpMyAdmin или MySQL."; $a->strings["Please see the file \"INSTALL.txt\"."] = "Пожалуйста, смотрите файл \"INSTALL.txt\"."; +$a->strings["Database already in use."] = ""; $a->strings["System check"] = "Проверить систему"; $a->strings["Check again"] = "Проверить еще раз"; $a->strings["Database connection"] = "Подключение к базе данных"; @@ -743,7 +869,7 @@ $a->strings["Your account email address must match this in order to use the web $a->strings["Please select a default timezone for your website"] = "Пожалуйста, выберите часовой пояс по умолчанию для вашего сайта"; $a->strings["Site settings"] = "Настройки сайта"; $a->strings["Could not find a command line version of PHP in the web server PATH."] = "Не удалось найти PATH веб-сервера в установках PHP."; -$a->strings["If you don't have a command line version of PHP installed on server, you will not be able to run background polling via cron. See <a href='http://friendica.com/node/27'>'Activating scheduled tasks'</a>"] = "Если на вашем сервере не установлена версия командной строки PHP, вы не будете иметь возможность запускать фоновые опросы через крон. См. <a href='http://friendica.com/node/27'> 'Активация запланированных задачах' </a>"; +$a->strings["If you don't have a command line version of PHP installed on server, you will not be able to run background polling via cron. See <a href='https://github.com/friendica/friendica/blob/master/doc/Install.md#set-up-the-poller'>'Setup the poller'</a>"] = ""; $a->strings["PHP executable path"] = "PHP executable path"; $a->strings["Enter full path to php executable. You can leave this blank to continue the installation."] = "Введите полный путь к исполняемому файлу PHP. Вы можете оставить это поле пустым, чтобы продолжить установку."; $a->strings["Command line PHP"] = "Command line PHP"; @@ -761,6 +887,7 @@ $a->strings["GD graphics PHP module"] = "GD graphics PHP модуль"; $a->strings["OpenSSL PHP module"] = "OpenSSL PHP модуль"; $a->strings["mysqli PHP module"] = "mysqli PHP модуль"; $a->strings["mb_string PHP module"] = "mb_string PHP модуль"; +$a->strings["mcrypt PHP module"] = ""; $a->strings["Apache mod_rewrite module"] = "Apache mod_rewrite module"; $a->strings["Error: Apache webserver mod-rewrite module is required but not installed."] = "Ошибка: необходим модуль веб-сервера Apache mod-rewrite, но он не установлен."; $a->strings["Error: libCURL PHP module required but not installed."] = "Ошибка: необходим libCURL PHP модуль, но он не установлен."; @@ -768,6 +895,9 @@ $a->strings["Error: GD graphics PHP module with JPEG support required but not in $a->strings["Error: openssl PHP module required but not installed."] = "Ошибка: необходим PHP модуль OpenSSL, но он не установлен."; $a->strings["Error: mysqli PHP module required but not installed."] = "Ошибка: необходим PHP модуль MySQLi, но он не установлен."; $a->strings["Error: mb_string PHP module required but not installed."] = "Ошибка: необходим PHP модуль mb_string, но он не установлен."; +$a->strings["Error: mcrypt PHP module required but not installed."] = ""; +$a->strings["Function mcrypt_create_iv() is not defined. This is needed to enable RINO2 encryption layer."] = ""; +$a->strings["mcrypt_create_iv() function"] = ""; $a->strings["The web installer needs to be able to create a file called \".htconfig.php\" in the top folder of your web server and it is unable to do so."] = "Веб-инсталлятору требуется создать файл с именем \". htconfig.php\" в верхней папке веб-сервера, но он не в состоянии это сделать."; $a->strings["This is most often a permission setting, as the web server may not be able to write files in your folder - even if you can."] = "Это наиболее частые параметры разрешений, когда веб-сервер не может записать файлы в папке - даже если вы можете."; $a->strings["At the end of this procedure, we will give you a text to save in a file named .htconfig.php in your Friendica top folder."] = "В конце этой процедуры, мы дадим вам текст, для сохранения в файле с именем .htconfig.php в корневой папке, где установлена Friendica."; @@ -780,6 +910,8 @@ $a->strings["Note: as a security measure, you should give the web server write a $a->strings["view/smarty3 is writable"] = "view/smarty3 доступен для записи"; $a->strings["Url rewrite in .htaccess is not working. Check your server configuration."] = "Url rewrite в .htaccess не работает. Проверьте конфигурацию вашего сервера.."; $a->strings["Url rewrite is working"] = "Url rewrite работает"; +$a->strings["ImageMagick PHP extension is installed"] = ""; +$a->strings["ImageMagick supports GIF"] = ""; $a->strings["The database configuration file \".htconfig.php\" could not be written. Please use the enclosed text to create a configuration file in your web server root."] = "Файл конфигурации базы данных \".htconfig.php\" не могла быть записан. Пожалуйста, используйте приложенный текст, чтобы создать конфигурационный файл в корневом каталоге веб-сервера."; $a->strings["<h1>What next</h1>"] = "<h1>Что далее</h1>"; $a->strings["IMPORTANT: You will need to [manually] setup a scheduled task for the poller."] = "ВАЖНО: Вам нужно будет [вручную] установить запланированное задание для регистратора."; @@ -795,21 +927,19 @@ $a->strings["%1\$s welcomes %2\$s"] = "%1\$s добро пожаловать %2\ $a->strings["Welcome to %s"] = "Добро пожаловать на %s!"; $a->strings["Sorry, maybe your upload is bigger than the PHP configuration allows"] = ""; $a->strings["Or - did you try to upload an empty file?"] = ""; -$a->strings["File exceeds size limit of %d"] = "Файл превышает предельный размер %d"; +$a->strings["File exceeds size limit of %s"] = ""; $a->strings["File upload failed."] = "Загрузка файла не удалась."; -$a->strings["Profile Match"] = "Похожие профили"; $a->strings["No keywords to match. Please add keywords to your default profile."] = "Нет соответствующих ключевых слов. Пожалуйста, добавьте ключевые слова для вашего профиля по умолчанию."; $a->strings["is interested in:"] = "интересуется:"; -$a->strings["Connect"] = "Подключить"; +$a->strings["Profile Match"] = "Похожие профили"; $a->strings["link"] = "ссылка"; $a->strings["Not available."] = "Недоступно."; $a->strings["Community"] = "Сообщество"; $a->strings["No results."] = "Нет результатов."; $a->strings["everybody"] = "каждый"; -$a->strings["Additional features"] = "Дополнительные возможности"; -$a->strings["Display"] = ""; -$a->strings["Social Networks"] = ""; -$a->strings["Delegations"] = ""; +$a->strings["Display"] = "Внешний вид"; +$a->strings["Social Networks"] = "Социальные сети"; +$a->strings["Delegations"] = "Делегирование"; $a->strings["Connected apps"] = "Подключенные приложения"; $a->strings["Export personal data"] = "Экспорт личных данных"; $a->strings["Remove account"] = "Удалить аккаунт"; @@ -843,14 +973,20 @@ $a->strings["No name"] = "Нет имени"; $a->strings["Remove authorization"] = "Удалить авторизацию"; $a->strings["No Plugin settings configured"] = "Нет сконфигурированных настроек плагина"; $a->strings["Plugin Settings"] = "Настройки плагина"; -$a->strings["Off"] = ""; -$a->strings["On"] = ""; $a->strings["Additional Features"] = "Дополнительные возможности"; +$a->strings["General Social Media Settings"] = ""; +$a->strings["Disable intelligent shortening"] = ""; +$a->strings["Normally the system tries to find the best link to add to shortened posts. If this option is enabled then every shortened post will always point to the original friendica post."] = ""; +$a->strings["Automatically follow any GNU Social (OStatus) followers/mentioners"] = ""; +$a->strings["If you receive a message from an unknown OStatus user, this option decides what to do. If it is checked, a new contact will be created for every unknown user."] = ""; +$a->strings["Your legacy GNU Social account"] = ""; +$a->strings["If you enter your old GNU Social/Statusnet account name here (in the format user@domain.tld), your contacts will be added automatically. The field will be emptied when done."] = ""; +$a->strings["Repair OStatus subscriptions"] = ""; $a->strings["Built-in support for %s connectivity is %s"] = "Встроенная поддержка для %s подключение %s"; $a->strings["Diaspora"] = "Diaspora"; $a->strings["enabled"] = "подключено"; $a->strings["disabled"] = "отключено"; -$a->strings["StatusNet"] = "StatusNet"; +$a->strings["GNU Social (OStatus)"] = ""; $a->strings["Email access is disabled on this site."] = "Доступ эл. почты отключен на этом сайте."; $a->strings["Email/Mailbox Setup"] = "Настройка эл. почты / почтового ящика"; $a->strings["If you wish to communicate with email contacts using this service (optional), please specify how to connect to your mailbox."] = "Если вы хотите общаться с Email контактами, используя этот сервис (по желанию), пожалуйста, уточните, как подключиться к вашему почтовому ящику."; @@ -871,14 +1007,17 @@ $a->strings["Display Settings"] = "Параметры дисплея"; $a->strings["Display Theme:"] = "Показать тему:"; $a->strings["Mobile Theme:"] = "Мобильная тема:"; $a->strings["Update browser every xx seconds"] = "Обновление браузера каждые хх секунд"; -$a->strings["Minimum of 10 seconds, no maximum"] = "Минимум 10 секунд, максимума нет"; +$a->strings["Minimum of 10 seconds. Enter -1 to disable it."] = ""; $a->strings["Number of items to display per page:"] = "Количество элементов, отображаемых на одной странице:"; $a->strings["Maximum of 100 items"] = "Максимум 100 элементов"; $a->strings["Number of items to display per page when viewed from mobile device:"] = "Количество элементов на странице, когда просмотр осуществляется с мобильных устройств:"; $a->strings["Don't show emoticons"] = "не показывать emoticons"; +$a->strings["Calendar"] = ""; +$a->strings["Beginning of week:"] = ""; $a->strings["Don't show notices"] = ""; $a->strings["Infinite scroll"] = "Бесконечная прокрутка"; $a->strings["Automatic updates only at the top of the network page"] = ""; +$a->strings["Theme settings"] = "Настройки темы"; $a->strings["User Types"] = ""; $a->strings["Community Types"] = ""; $a->strings["Normal Account Page"] = "Стандартная страница аккаунта"; @@ -894,7 +1033,6 @@ $a->strings["Private forum - approved members only"] = "Приватный фо $a->strings["OpenID:"] = "OpenID:"; $a->strings["(Optional) Allow this OpenID to login to this account."] = "(Необязательно) Разрешить этому OpenID входить в этот аккаунт"; $a->strings["Publish your default profile in your local site directory?"] = "Публиковать ваш профиль по умолчанию в вашем локальном каталоге на сайте?"; -$a->strings["No"] = "Нет"; $a->strings["Publish your default profile in the global social directory?"] = "Публиковать ваш профиль по умолчанию в глобальном социальном каталоге?"; $a->strings["Hide your contact/friend list from viewers of your default profile?"] = "Скрывать ваш список контактов/друзей от посетителей вашего профиля по умолчанию?"; $a->strings["Hide your profile details from unknown viewers?"] = "Скрыть данные профиля из неизвестных зрителей?"; @@ -904,7 +1042,7 @@ $a->strings["Allow friends to tag your posts?"] = "Разрешить друзь $a->strings["Allow us to suggest you as a potential friend to new members?"] = "Позвольть предлогать Вам потенциальных друзей?"; $a->strings["Permit unknown people to send you private mail?"] = "Разрешить незнакомым людям отправлять вам личные сообщения?"; $a->strings["Profile is <strong>not published</strong>."] = "Профиль <strong>не публикуется</strong>."; -$a->strings["Your Identity Address is"] = "Ваш идентификационный адрес"; +$a->strings["Your Identity Address is <strong>'%s'</strong> or '%s'."] = ""; $a->strings["Automatically expire posts after this many days:"] = "Автоматическое истекание срока действия сообщения после стольких дней:"; $a->strings["If empty, posts will not expire. Expired posts will be deleted"] = "Если пусто, срок действия сообщений не будет ограничен. Сообщения с истекшим сроком действия будут удалены"; $a->strings["Advanced expiration settings"] = "Настройки расширенного окончания срока действия"; @@ -915,7 +1053,7 @@ $a->strings["Expire starred posts:"] = "Срок хранения усеянны $a->strings["Expire photos:"] = "Срок хранения фотографий:"; $a->strings["Only expire posts by others:"] = "Только устаревшие посты других:"; $a->strings["Account Settings"] = "Настройки аккаунта"; -$a->strings["Password Settings"] = "Настройка пароля"; +$a->strings["Password Settings"] = "Смена пароля"; $a->strings["New Password:"] = "Новый пароль:"; $a->strings["Confirm:"] = "Подтвердите:"; $a->strings["Leave password fields blank unless changing"] = "Оставьте поля пароля пустыми, если он не изменяется"; @@ -926,6 +1064,8 @@ $a->strings["Basic Settings"] = "Основные параметры"; $a->strings["Full Name:"] = "Полное имя:"; $a->strings["Email Address:"] = "Адрес электронной почты:"; $a->strings["Your Timezone:"] = "Ваш часовой пояс:"; +$a->strings["Your Language:"] = ""; +$a->strings["Set the language we use to show you friendica interface and to send you emails"] = ""; $a->strings["Default Post Location:"] = "Местонахождение по умолчанию:"; $a->strings["Use Browser Location:"] = "Использовать определение местоположения браузером:"; $a->strings["Security and Privacy Settings"] = "Параметры безопасности и конфиденциальности"; @@ -953,11 +1093,13 @@ $a->strings["You receive a private message"] = "Вы получаете личн $a->strings["You receive a friend suggestion"] = "Вы полулили предложение о добавлении в друзья"; $a->strings["You are tagged in a post"] = "Вы отмечены в посте"; $a->strings["You are poked/prodded/etc. in a post"] = ""; +$a->strings["Activate desktop notifications"] = ""; +$a->strings["Show desktop popup on new notifications"] = ""; $a->strings["Text-only notification emails"] = ""; $a->strings["Send text only notification emails, without the html part"] = ""; -$a->strings["Advanced Account/Page Type Settings"] = "Расширенные настройки типа аккаунта/страницы"; +$a->strings["Advanced Account/Page Type Settings"] = "Расширенные настройки учётной записи"; $a->strings["Change the behaviour of this account for special situations"] = "Измените поведение этого аккаунта в специальных ситуациях"; -$a->strings["Relocate"] = "Переместить"; +$a->strings["Relocate"] = "Перемещение"; $a->strings["If you have moved this profile from another server, and some of your contacts don't receive your updates, try pushing this button."] = "Если вы переместили эту анкету с другого сервера, и некоторые из ваших контактов не получили ваши обновления, попробуйте нажать эту кнопку."; $a->strings["Resend relocate message to contacts"] = "Отправить перемещённые сообщения контактам"; $a->strings["This introduction has already been accepted."] = "Этот запрос был уже принят."; @@ -968,6 +1110,7 @@ $a->strings["%d required parameter was not found at the given location"] = array 0 => "%d требуемый параметр не был найден в заданном месте", 1 => "%d требуемых параметров не были найдены в заданном месте", 2 => "%d требуемых параметров не были найдены в заданном месте", + 3 => "%d требуемых параметров не были найдены в заданном месте", ); $a->strings["Introduction complete."] = "Запрос создан."; $a->strings["Unrecoverable protocol error."] = "Неисправимая ошибка протокола."; @@ -978,32 +1121,28 @@ $a->strings["Friends are advised to please try again in 24 hours."] = "Друз $a->strings["Invalid locator"] = "Недопустимый локатор"; $a->strings["Invalid email address."] = "Неверный адрес электронной почты."; $a->strings["This account has not been configured for email. Request failed."] = "Этот аккаунт не настроен для электронной почты. Запрос не удался."; -$a->strings["Unable to resolve your name at the provided location."] = "Не удается установить ваше имя на предложенном местоположении."; $a->strings["You have already introduced yourself here."] = "Вы уже ввели информацию о себе здесь."; $a->strings["Apparently you are already friends with %s."] = "Похоже, что вы уже друзья с %s."; $a->strings["Invalid profile URL."] = "Неверный URL профиля."; $a->strings["Disallowed profile URL."] = "Запрещенный URL профиля."; $a->strings["Your introduction has been sent."] = "Ваш запрос отправлен."; +$a->strings["Remote subscription can't be done for your network. Please subscribe directly on your system."] = ""; $a->strings["Please login to confirm introduction."] = "Для подтверждения запроса войдите пожалуйста с паролем."; $a->strings["Incorrect identity currently logged in. Please login to <strong>this</strong> profile."] = "Неверно идентифицирован вход. Пожалуйста, войдите в <strong>этот</strong> профиль."; +$a->strings["Confirm"] = "Подтвердить"; $a->strings["Hide this contact"] = "Скрыть этот контакт"; $a->strings["Welcome home %s."] = "Добро пожаловать домой, %s!"; $a->strings["Please confirm your introduction/connection request to %s."] = "Пожалуйста, подтвердите краткую информацию / запрос на подключение к %s."; -$a->strings["Confirm"] = "Подтвердить"; $a->strings["Please enter your 'Identity Address' from one of the following supported communications networks:"] = "Пожалуйста, введите ваш 'идентификационный адрес' одной из следующих поддерживаемых социальных сетей:"; -$a->strings["If you are not yet a member of the free social web, <a href=\"http://dir.friendica.com/siteinfo\">follow this link to find a public Friendica site and join us today</a>."] = "Если вы еще не являетесь членом свободной социальной сети, перейдите по <a href=\"http://dir.friendica.com/siteinfo\"> этой ссылке, чтобы найти публичный сервер Friendica и присоединиться к нам сейчас </a>."; +$a->strings["If you are not yet a member of the free social web, <a href=\"%s/siteinfo\">follow this link to find a public Friendica site and join us today</a>."] = ""; $a->strings["Friend/Connection Request"] = "Запрос в друзья / на подключение"; $a->strings["Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca"] = "Примеры: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca"; -$a->strings["Please answer the following:"] = "Пожалуйста, ответьте следующее:"; -$a->strings["Does %s know you?"] = "%s знает вас?"; -$a->strings["Add a personal note:"] = "Добавить личную заметку:"; $a->strings["Friendica"] = "Friendica"; $a->strings["StatusNet/Federated Social Web"] = "StatusNet / Federated Social Web"; $a->strings[" - please do not use this form. Instead, enter %s into your Diaspora search bar."] = "Участники сети Diaspora: пожалуйста, не пользуйтесь этой формой. Вместо этого введите %s в строке поиска Diaspora"; -$a->strings["Your Identity Address:"] = "Ваш идентификационный адрес:"; -$a->strings["Submit Request"] = "Отправить запрос"; $a->strings["Registration successful. Please check your email for further instructions."] = "Регистрация успешна. Пожалуйста, проверьте свою электронную почту для получения дальнейших инструкций."; $a->strings["Failed to send email message. Here your accout details:<br> login: %s<br> password: %s<br><br>You can change your password after login."] = ""; +$a->strings["Registration successful."] = ""; $a->strings["Your registration can not be processed."] = "Ваша регистрация не может быть обработана."; $a->strings["Your registration is pending approval by the site owner."] = "Ваша регистрация в ожидании одобрения владельцем сайта."; $a->strings["This site has exceeded the number of allowed daily account registrations. Please try again tomorrow."] = "Этот сайт превысил допустимое количество ежедневных регистраций. Пожалуйста, повторите попытку завтра."; @@ -1013,24 +1152,27 @@ $a->strings["Your OpenID (optional): "] = "Ваш OpenID (необязатель $a->strings["Include your profile in member directory?"] = "Включить ваш профиль в каталог участников?"; $a->strings["Membership on this site is by invitation only."] = "Членство на сайте только по приглашению."; $a->strings["Your invitation ID: "] = "ID вашего приглашения:"; -$a->strings["Your Full Name (e.g. Joe Smith): "] = "Ваше полное имя (например, Joe Smith): "; +$a->strings["Your Full Name (e.g. Joe Smith, real or real-looking): "] = ""; $a->strings["Your Email Address: "] = "Ваш адрес электронной почты: "; +$a->strings["Leave empty for an auto generated password."] = ""; $a->strings["Choose a profile nickname. This must begin with a text character. Your profile address on this site will then be '<strong>nickname@\$sitename</strong>'."] = "Выбор псевдонима профиля. Он должен начинаться с буквы. Адрес вашего профиля на данном сайте будет в этом случае '<strong>nickname@\$sitename</strong>'."; $a->strings["Choose a nickname: "] = "Выберите псевдоним: "; $a->strings["Register"] = "Регистрация"; $a->strings["Import"] = "Импорт"; $a->strings["Import your profile to this friendica instance"] = "Импорт своего профиля в этот экземпляр friendica"; $a->strings["System down for maintenance"] = "Система закрыта на техническое обслуживание"; +$a->strings["Only logged in users are permitted to perform a search."] = ""; +$a->strings["Too Many Requests"] = ""; +$a->strings["Only one search per minute is permitted for not logged in users."] = ""; $a->strings["Search"] = "Поиск"; -$a->strings["Global Directory"] = "Глобальный каталог"; -$a->strings["Find on this site"] = "Найти на этом сайте"; -$a->strings["Site Directory"] = "Каталог сайта"; -$a->strings["Age: "] = "Возраст: "; -$a->strings["Gender: "] = "Пол: "; -$a->strings["Gender:"] = "Пол:"; +$a->strings["Items tagged with: %s"] = ""; +$a->strings["Search results for: %s"] = ""; $a->strings["Status:"] = "Статус:"; $a->strings["Homepage:"] = "Домашняя страничка:"; -$a->strings["About:"] = "О себе:"; +$a->strings["Global Directory"] = "Глобальный каталог"; +$a->strings["Find on this site"] = "Найти на этом сайте"; +$a->strings["Finding:"] = ""; +$a->strings["Site Directory"] = "Каталог сайта"; $a->strings["No entries (some entries may be hidden)."] = "Нет записей (некоторые записи могут быть скрыты)."; $a->strings["No potential page delegates located."] = ""; $a->strings["Delegate Page Management"] = "Делегировать управление страницей"; @@ -1040,7 +1182,6 @@ $a->strings["Existing Page Delegates"] = "Существующие уполно $a->strings["Potential Delegates"] = "Возможные доверенные лица"; $a->strings["Add"] = "Добавить"; $a->strings["No entries."] = "Нет записей."; -$a->strings["Common Friends"] = "Общие друзья"; $a->strings["No contacts in common."] = "Нет общих контактов."; $a->strings["Export account"] = "Экспорт аккаунта"; $a->strings["Export your account info and contacts. Use this to make a backup of your account and/or to move it to another server."] = "Экспорт ваших регистрационных данные и контактов. Используйте, чтобы создать резервную копию вашего аккаунта и/или переместить его на другой сервер."; @@ -1050,9 +1191,9 @@ $a->strings["%1\$s is currently %2\$s"] = ""; $a->strings["Mood"] = "Настроение"; $a->strings["Set your current mood and tell your friends"] = "Напишите о вашем настроении и расскажите своим друзьям"; $a->strings["Do you really want to delete this suggestion?"] = "Вы действительно хотите удалить это предложение?"; -$a->strings["Friend Suggestions"] = "Предложения друзей"; $a->strings["No suggestions available. If this is a new site, please try again in 24 hours."] = "Нет предложений. Если это новый сайт, пожалуйста, попробуйте снова через 24 часа."; $a->strings["Ignore/Hide"] = "Проигнорировать/Скрыть"; +$a->strings["Friend Suggestions"] = "Предложения друзей"; $a->strings["Profile deleted."] = "Профиль удален."; $a->strings["Profile-"] = "Профиль-"; $a->strings["New profile created."] = "Новый профиль создан."; @@ -1079,6 +1220,7 @@ $a->strings[" - Visit %1\$s's %2\$s"] = " - Посетить профиль %1\$ $a->strings["%1\$s has an updated %2\$s, changing %3\$s."] = ""; $a->strings["Hide contacts and friends:"] = ""; $a->strings["Hide your contact/friend list from viewers of this profile?"] = "Скрывать ваш список контактов / друзей от посетителей этого профиля?"; +$a->strings["Show more profile fields:"] = ""; $a->strings["Edit Profile Details"] = "Редактировать детали профиля"; $a->strings["Change Profile Photo"] = "Изменить фото профиля"; $a->strings["View this profile"] = "Просмотреть этот профиль"; @@ -1094,7 +1236,7 @@ $a->strings["Profile Name:"] = "Имя профиля:"; $a->strings["Your Full Name:"] = "Ваше полное имя:"; $a->strings["Title/Description:"] = "Заголовок / Описание:"; $a->strings["Your Gender:"] = "Ваш пол:"; -$a->strings["Birthday (%s):"] = "День рождения (%s):"; +$a->strings["Birthday :"] = ""; $a->strings["Street Address:"] = "Адрес:"; $a->strings["Locality/City:"] = "Город / Населенный пункт:"; $a->strings["Postal/Zip Code:"] = "Почтовый индекс:"; @@ -1127,6 +1269,7 @@ $a->strings["Love/romance"] = "Любовь / романтика"; $a->strings["Work/employment"] = "Работа / занятость"; $a->strings["School/education"] = "Школа / образование"; $a->strings["This is your <strong>public</strong> profile.<br />It <strong>may</strong> be visible to anybody using the internet."] = "Это ваш <strong>публичный</strong> профиль. <br /> Он <strong>может</strong> быть виден каждому через Интернет."; +$a->strings["Age: "] = "Возраст: "; $a->strings["Edit/Manage Profiles"] = "Редактировать профиль"; $a->strings["Change profile photo"] = "Изменить фото профиля"; $a->strings["Create New Profile"] = "Создать новый профиль"; @@ -1136,7 +1279,7 @@ $a->strings["Edit visibility"] = "Редактировать видимость" $a->strings["Item not found"] = "Элемент не найден"; $a->strings["Edit post"] = "Редактировать сообщение"; $a->strings["upload photo"] = "загрузить фото"; -$a->strings["Attach file"] = "Приложить файл"; +$a->strings["Attach file"] = "Прикрепить файл"; $a->strings["attach file"] = "приложить файл"; $a->strings["web link"] = "веб-ссылка"; $a->strings["Insert video link"] = "Вставить ссылку видео"; @@ -1157,6 +1300,7 @@ $a->strings["This is Friendica, version"] = "Это Friendica, версия"; $a->strings["running at web location"] = "работает на веб-узле"; $a->strings["Please visit <a href=\"http://friendica.com\">Friendica.com</a> to learn more about the Friendica project."] = "Пожалуйста, посетите сайт <a href=\"http://friendica.com\">Friendica.com</a>, чтобы узнать больше о проекте Friendica."; $a->strings["Bug reports and issues: please visit"] = "Отчет об ошибках и проблемах: пожалуйста, посетите"; +$a->strings["the bugtracker at github"] = ""; $a->strings["Suggestions, praise, donations, etc. - please email \"Info\" at Friendica - dot com"] = "Предложения, похвала, пожертвования? Пишите на \"info\" на Friendica - точка com"; $a->strings["Installed plugins/addons/apps:"] = "Установленные плагины / добавки / приложения:"; $a->strings["No installed plugins/addons/apps"] = "Нет установленных плагинов / добавок / приложений"; @@ -1179,6 +1323,8 @@ $a->strings["poke, prod or do other things to somebody"] = ""; $a->strings["Recipient"] = "Получатель"; $a->strings["Choose what you wish to do to recipient"] = "Выберите действия для получателя"; $a->strings["Make this post private"] = "Сделать эту запись личной"; +$a->strings["Resubscribing to OStatus contacts"] = ""; +$a->strings["Error"] = "Ошибка"; $a->strings["Total invitation limit exceeded."] = "Превышен общий лимит приглашений."; $a->strings["%s : Not a valid email address."] = "%s: Неверный адрес электронной почты."; $a->strings["Please join us on Friendica"] = "Пожалуйста, присоединяйтесь к нам на Friendica"; @@ -1188,6 +1334,7 @@ $a->strings["%d message sent."] = array( 0 => "%d сообщение отправлено.", 1 => "%d сообщений отправлено.", 2 => "%d сообщений отправлено.", + 3 => "%d сообщений отправлено.", ); $a->strings["You have no more invitations available"] = "У вас нет больше приглашений"; $a->strings["Visit %s for a list of public sites that you can join. Friendica members on other sites can all connect with each other, as well as with members of many other social networks."] = "Посетите %s со списком общедоступных сайтов, к которым вы можете присоединиться. Все участники Friendica на других сайтах могут соединиться друг с другом, а также с участниками многих других социальных сетей."; @@ -1201,7 +1348,7 @@ $a->strings["You will need to supply this invitation code: \$invite_code"] = "В $a->strings["Once you have registered, please connect with me via my profile page at:"] = "После того как вы зарегистрировались, пожалуйста, свяжитесь со мной через мою страницу профиля по адресу:"; $a->strings["For more information about the Friendica project and why we feel it is important, please visit http://friendica.com"] = "Для получения более подробной информации о проекте Friendica, пожалуйста, посетите http://friendica.com"; $a->strings["Photo Albums"] = "Фотоальбомы"; -$a->strings["Contact Photos"] = "Фотографии контакта"; +$a->strings["Recent Photos"] = "Последние фото"; $a->strings["Upload New Photos"] = "Загрузить новые фото"; $a->strings["Contact information unavailable"] = "Информация о контакте недоступна"; $a->strings["Album not found."] = "Альбом не найден."; @@ -1211,7 +1358,6 @@ $a->strings["Delete Photo"] = "Удалить фото"; $a->strings["Do you really want to delete this photo?"] = "Вы действительно хотите удалить эту фотографию?"; $a->strings["%1\$s was tagged in %2\$s by %3\$s"] = "%1\$s отмечен/а/ в %2\$s by %3\$s"; $a->strings["a photo"] = "фото"; -$a->strings["Image exceeds size limit of "] = "Размер фото превышает лимит "; $a->strings["Image file is empty."] = "Файл изображения пуст."; $a->strings["No photos selected"] = "Не выбрано фото."; $a->strings["You have used %1$.2f Mbytes of %2$.2f Mbytes photo storage."] = "Вы использовали %1$.2f мегабайт из %2$.2f возможных для хранения фотографий."; @@ -1234,23 +1380,33 @@ $a->strings["Use as profile photo"] = "Использовать как фото $a->strings["View Full Size"] = "Просмотреть полный размер"; $a->strings["Tags: "] = "Ключевые слова: "; $a->strings["[Remove any tag]"] = "[Удалить любое ключевое слово]"; -$a->strings["Rotate CW (right)"] = "Поворот по часовой стрелке (направо)"; -$a->strings["Rotate CCW (left)"] = "Поворот против часовой стрелки (налево)"; $a->strings["New album name"] = "Название нового альбома"; $a->strings["Caption"] = "Подпись"; $a->strings["Add a Tag"] = "Добавить ключевое слово (таг)"; $a->strings["Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping"] = "Пример: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping"; +$a->strings["Do not rotate"] = ""; +$a->strings["Rotate CW (right)"] = "Поворот по часовой стрелке (направо)"; +$a->strings["Rotate CCW (left)"] = "Поворот против часовой стрелки (налево)"; $a->strings["Private photo"] = "Личное фото"; $a->strings["Public photo"] = "Публичное фото"; $a->strings["Share"] = "Поделиться"; -$a->strings["Recent Photos"] = "Последние фото"; +$a->strings["Attending"] = array( + 0 => "", + 1 => "", + 2 => "", + 3 => "", +); +$a->strings["Not attending"] = ""; +$a->strings["Might attend"] = ""; +$a->strings["Map"] = "Карта"; +$a->strings["Not Extended"] = ""; $a->strings["Account approved."] = "Аккаунт утвержден."; $a->strings["Registration revoked for %s"] = "Регистрация отменена для %s"; $a->strings["Please login."] = "Пожалуйста, войдите с паролем."; $a->strings["Move account"] = "Удалить аккаунт"; $a->strings["You can import an account from another Friendica server."] = "Вы можете импортировать учетную запись с другого сервера Friendica."; $a->strings["You need to export your account from the old server and upload it here. We will recreate your old account here with all your contacts. We will try also to inform your friends that you moved here."] = "Вам нужно экспортировать свой аккаунт со старого сервера и загрузить его сюда. Мы восстановим ваш старый аккаунт здесь со всеми вашими контактами. Мы постараемся также сообщить друзьям, что вы переехали сюда."; -$a->strings["This feature is experimental. We can't import contacts from the OStatus network (statusnet/identi.ca) or from Diaspora"] = "Это экспериментальная функция. Мы не можем импортировать контакты из сети OStatus (StatusNet / identi.ca) или из Diaspora"; +$a->strings["This feature is experimental. We can't import contacts from the OStatus network (GNU Social/Statusnet) or from Diaspora"] = ""; $a->strings["Account file"] = "Файл аккаунта"; $a->strings["To export your account, go to \"Settings->Export your personal data\" and select \"Export account\""] = "Для экспорта аккаунта, перейдите в \"Настройки->Экспортировать ваши данные\" и выберите \"Экспорт аккаунта\""; $a->strings["Item not available."] = "Пункт не доступен."; @@ -1269,31 +1425,13 @@ $a->strings["Website Terms of Service"] = "Правила сайта"; $a->strings["terms of service"] = "правила"; $a->strings["Website Privacy Policy"] = "Политика конфиденциальности сервера"; $a->strings["privacy policy"] = "политика конфиденциальности"; -$a->strings["Requested account is not available."] = "Запрашиваемый профиль недоступен."; -$a->strings["Edit profile"] = "Редактировать профиль"; -$a->strings["Message"] = "Сообщение"; -$a->strings["Profiles"] = "Профили"; -$a->strings["Manage/edit profiles"] = "Управление / редактирование профилей"; -$a->strings["Network:"] = ""; -$a->strings["g A l F d"] = "g A l F d"; -$a->strings["F d"] = "F d"; -$a->strings["[today]"] = "[сегодня]"; -$a->strings["Birthday Reminders"] = "Напоминания о днях рождения"; -$a->strings["Birthdays this week:"] = "Дни рождения на этой неделе:"; -$a->strings["[No description]"] = "[без описания]"; -$a->strings["Event Reminders"] = "Напоминания о мероприятиях"; -$a->strings["Events this week:"] = "Мероприятия на этой неделе:"; -$a->strings["Status"] = "Статус"; -$a->strings["Status Messages and Posts"] = "Сообщение статуса и посты"; -$a->strings["Profile Details"] = "Детали профиля"; -$a->strings["Videos"] = "Видео"; -$a->strings["Events and Calendar"] = "Календарь и события"; -$a->strings["Only You Can See This"] = "Только вы можете это видеть"; $a->strings["This entry was edited"] = "Эта запись была отредактирована"; +$a->strings["I will attend"] = ""; +$a->strings["I will not attend"] = ""; +$a->strings["I might attend"] = ""; $a->strings["ignore thread"] = ""; $a->strings["unignore thread"] = ""; $a->strings["toggle ignore status"] = ""; -$a->strings["ignored"] = ""; $a->strings["Categories:"] = "Категории:"; $a->strings["Filed under:"] = "В рубрике:"; $a->strings["via"] = "через"; @@ -1311,10 +1449,10 @@ $a->strings["%d invitation available"] = array( 0 => "%d приглашение доступно", 1 => "%d приглашений доступно", 2 => "%d приглашений доступно", + 3 => "%d приглашений доступно", ); $a->strings["Find People"] = "Поиск людей"; $a->strings["Enter name or interest"] = "Введите имя или интерес"; -$a->strings["Connect/Follow"] = "Подключиться/Следовать"; $a->strings["Examples: Robert Morgenstein, Fishing"] = "Примеры: Роберт Morgenstein, Рыбалка"; $a->strings["Similar Interests"] = "Похожие интересы"; $a->strings["Random Profile"] = "Случайный профиль"; @@ -1324,19 +1462,29 @@ $a->strings["All Networks"] = "Все сети"; $a->strings["Saved Folders"] = "Сохранённые папки"; $a->strings["Everything"] = "Всё"; $a->strings["Categories"] = "Категории"; +$a->strings["%d contact in common"] = array( + 0 => "%d Контакт", + 1 => "%d Контактов", + 2 => "%d Контактов", + 3 => "%d Контактов", +); $a->strings["General Features"] = "Основные возможности"; $a->strings["Multiple Profiles"] = "Несколько профилей"; $a->strings["Ability to create multiple profiles"] = "Возможность создания нескольких профилей"; -$a->strings["Post Composition Features"] = ""; +$a->strings["Photo Location"] = ""; +$a->strings["Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map."] = ""; +$a->strings["Post Composition Features"] = "Составление сообщений"; $a->strings["Richtext Editor"] = "Редактор RTF"; $a->strings["Enable richtext editor"] = "Включить редактор RTF"; -$a->strings["Post Preview"] = "предварительный просмотр"; +$a->strings["Post Preview"] = "Предварительный просмотр"; $a->strings["Allow previewing posts and comments before publishing them"] = "Разрешить предпросмотр сообщения и комментария перед их публикацией"; $a->strings["Auto-mention Forums"] = ""; $a->strings["Add/remove mention when a fourm page is selected/deselected in ACL window."] = ""; $a->strings["Network Sidebar Widgets"] = "Виджет боковой панели \"Сеть\""; $a->strings["Search by Date"] = "Поиск по датам"; $a->strings["Ability to select posts by date ranges"] = "Возможность выбора постов по диапазону дат"; +$a->strings["List Forums"] = ""; +$a->strings["Enable widget to display the forums your are connected with"] = ""; $a->strings["Group Filter"] = "Фильтр групп"; $a->strings["Enable widget to display Network posts only from selected group"] = "Включить виджет для отображения сообщений сети только от выбранной группы"; $a->strings["Network Filter"] = "Фильтр сети"; @@ -1365,6 +1513,8 @@ $a->strings["Star Posts"] = "Популярные посты"; $a->strings["Ability to mark special posts with a star indicator"] = "Возможность отметить специальные сообщения индикатором популярности"; $a->strings["Mute Post Notifications"] = ""; $a->strings["Ability to mute notifications for a thread"] = ""; +$a->strings["Advanced Profile Settings"] = "Расширенные настройки профиля"; +$a->strings["Show visitors public community forums at the Advanced Profile Page"] = ""; $a->strings["Connect URL missing."] = "Connect-URL отсутствует."; $a->strings["This site is not configured to allow communications with other networks."] = "Данный сайт не настроен так, чтобы держать связь с другими сетями."; $a->strings["No compatible communication protocols or feeds were discovered."] = "Обнаружены несовместимые протоколы связи или каналы."; @@ -1381,18 +1531,17 @@ $a->strings["A deleted group with this name was revived. Existing item permissio $a->strings["Default privacy group for new contacts"] = "Группа доступа по умолчанию для новых контактов"; $a->strings["Everybody"] = "Каждый"; $a->strings["edit"] = "редактировать"; +$a->strings["Edit groups"] = ""; $a->strings["Edit group"] = "Редактировать группу"; $a->strings["Create a new group"] = "Создать новую группу"; $a->strings["Contacts not in any group"] = "Контакты не состоят в группе"; $a->strings["Miscellaneous"] = "Разное"; -$a->strings["year"] = "год"; -$a->strings["month"] = "мес."; -$a->strings["day"] = "день"; +$a->strings["YYYY-MM-DD or MM-DD"] = ""; $a->strings["never"] = "никогда"; $a->strings["less than a second ago"] = "менее сек. назад"; +$a->strings["year"] = "год"; $a->strings["years"] = "лет"; $a->strings["months"] = "мес."; -$a->strings["week"] = "неделя"; $a->strings["weeks"] = "недель"; $a->strings["days"] = "дней"; $a->strings["hour"] = "час"; @@ -1404,26 +1553,68 @@ $a->strings["seconds"] = "сек."; $a->strings["%1\$d %2\$s ago"] = "%1\$d %2\$s назад"; $a->strings["%s's birthday"] = "день рождения %s"; $a->strings["Happy Birthday %s"] = "С днём рождения %s"; +$a->strings["Requested account is not available."] = "Запрашиваемый профиль недоступен."; +$a->strings["Edit profile"] = "Редактировать профиль"; +$a->strings["Atom feed"] = ""; +$a->strings["Message"] = "Сообщение"; +$a->strings["Profiles"] = "Профили"; +$a->strings["Manage/edit profiles"] = "Управление / редактирование профилей"; +$a->strings["g A l F d"] = "g A l F d"; +$a->strings["F d"] = "F d"; +$a->strings["[today]"] = "[сегодня]"; +$a->strings["Birthday Reminders"] = "Напоминания о днях рождения"; +$a->strings["Birthdays this week:"] = "Дни рождения на этой неделе:"; +$a->strings["[No description]"] = "[без описания]"; +$a->strings["Event Reminders"] = "Напоминания о мероприятиях"; +$a->strings["Events this week:"] = "Мероприятия на этой неделе:"; +$a->strings["j F, Y"] = "j F, Y"; +$a->strings["j F"] = "j F"; +$a->strings["Birthday:"] = "День рождения:"; +$a->strings["Age:"] = "Возраст:"; +$a->strings["for %1\$d %2\$s"] = ""; +$a->strings["Religion:"] = "Религия:"; +$a->strings["Hobbies/Interests:"] = "Хобби / Интересы:"; +$a->strings["Contact information and Social Networks:"] = "Информация о контакте и социальных сетях:"; +$a->strings["Musical interests:"] = "Музыкальные интересы:"; +$a->strings["Books, literature:"] = "Книги, литература:"; +$a->strings["Television:"] = "Телевидение:"; +$a->strings["Film/dance/culture/entertainment:"] = "Кино / Танцы / Культура / Развлечения:"; +$a->strings["Love/Romance:"] = "Любовь / Романтика:"; +$a->strings["Work/employment:"] = "Работа / Занятость:"; +$a->strings["School/education:"] = "Школа / Образование:"; +$a->strings["Forums:"] = ""; +$a->strings["Videos"] = "Видео"; +$a->strings["Events and Calendar"] = "Календарь и события"; +$a->strings["Only You Can See This"] = "Только вы можете это видеть"; +$a->strings["event"] = "мероприятие"; +$a->strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s нравится %3\$s от %2\$s "; +$a->strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s не нравится %3\$s от %2\$s "; +$a->strings["%1\$s is attending %2\$s's %3\$s"] = ""; +$a->strings["%1\$s is not attending %2\$s's %3\$s"] = ""; +$a->strings["%1\$s may attend %2\$s's %3\$s"] = ""; +$a->strings["Post to Email"] = "Отправить на Email"; +$a->strings["Connectors disabled, since \"%s\" is enabled."] = ""; $a->strings["Visible to everybody"] = "Видимо всем"; $a->strings["show"] = "показывать"; $a->strings["don't show"] = "не показывать"; +$a->strings["Close"] = "Закрыть"; $a->strings["[no subject]"] = "[без темы]"; $a->strings["stopped following"] = "остановлено следование"; -$a->strings["Poke"] = ""; $a->strings["View Status"] = "Просмотреть статус"; -$a->strings["View Profile"] = "Просмотреть профиль"; $a->strings["View Photos"] = "Просмотреть фото"; $a->strings["Network Posts"] = "Посты сети"; $a->strings["Edit Contact"] = "Редактировать контакт"; $a->strings["Drop Contact"] = "Удалить контакт"; $a->strings["Send PM"] = "Отправить ЛС"; +$a->strings["Poke"] = ""; $a->strings["Welcome "] = "Добро пожаловать, "; $a->strings["Please upload a profile photo."] = "Пожалуйста, загрузите фотографию профиля."; $a->strings["Welcome back "] = "Добро пожаловать обратно, "; $a->strings["The form security token was not correct. This probably happened because the form has been opened for too long (>3 hours) before submitting it."] = "Ключ формы безопасности неправильный. Вероятно, это произошло потому, что форма была открыта слишком долго (более 3 часов) до её отправки."; -$a->strings["event"] = "мероприятие"; +$a->strings["%1\$s attends %2\$s's %3\$s"] = ""; +$a->strings["%1\$s doesn't attend %2\$s's %3\$s"] = ""; +$a->strings["%1\$s attends maybe %2\$s's %3\$s"] = ""; $a->strings["%1\$s poked %2\$s"] = ""; -$a->strings["poked"] = ""; $a->strings["post/item"] = "пост/элемент"; $a->strings["%1\$s marked %2\$s's %3\$s as favorite"] = "%1\$s пометил %2\$s %3\$s как Фаворит"; $a->strings["remove"] = "удалить"; @@ -1431,24 +1622,58 @@ $a->strings["Delete Selected Items"] = "Удалить выбранные поз $a->strings["Follow Thread"] = ""; $a->strings["%s likes this."] = "%s нравится это."; $a->strings["%s doesn't like this."] = "%s не нравится это."; -$a->strings["<span %1\$s>%2\$d people</span> like this"] = "<span %1\$s>%2\$d людям</span> нравится это"; -$a->strings["<span %1\$s>%2\$d people</span> don't like this"] = "<span %1\$s>%2\$d людям</span> не нравится это"; +$a->strings["%s attends."] = ""; +$a->strings["%s doesn't attend."] = ""; +$a->strings["%s attends maybe."] = ""; $a->strings["and"] = "и"; $a->strings[", and %d other people"] = ", и %d других чел."; -$a->strings["%s like this."] = "%s нравится это."; -$a->strings["%s don't like this."] = "%s не нравится это."; +$a->strings["<span %1\$s>%2\$d people</span> like this"] = "<span %1\$s>%2\$d людям</span> нравится это"; +$a->strings["%s like this."] = ""; +$a->strings["<span %1\$s>%2\$d people</span> don't like this"] = "<span %1\$s>%2\$d людям</span> не нравится это"; +$a->strings["%s don't like this."] = ""; +$a->strings["<span %1\$s>%2\$d people</span> attend"] = ""; +$a->strings["%s attend."] = ""; +$a->strings["<span %1\$s>%2\$d people</span> don't attend"] = ""; +$a->strings["%s don't attend."] = ""; +$a->strings["<span %1\$s>%2\$d people</span> anttend maybe"] = ""; +$a->strings["%s anttend maybe."] = ""; $a->strings["Visible to <strong>everybody</strong>"] = "Видимое <strong>всем</strong>"; $a->strings["Please enter a video link/URL:"] = "Введите ссылку на видео link/URL:"; $a->strings["Please enter an audio link/URL:"] = "Введите ссылку на аудио link/URL:"; $a->strings["Tag term:"] = ""; $a->strings["Where are you right now?"] = "И где вы сейчас?"; $a->strings["Delete item(s)?"] = "Удалить елемент(ты)?"; -$a->strings["Post to Email"] = "Отправить на Email"; -$a->strings["Connectors disabled, since \"%s\" is enabled."] = ""; $a->strings["permissions"] = "разрешения"; $a->strings["Post to Groups"] = "Пост для групп"; $a->strings["Post to Contacts"] = "Пост для контактов"; $a->strings["Private post"] = "Личное сообщение"; +$a->strings["View all"] = ""; +$a->strings["Like"] = array( + 0 => "", + 1 => "", + 2 => "", + 3 => "", +); +$a->strings["Dislike"] = array( + 0 => "", + 1 => "", + 2 => "", + 3 => "", +); +$a->strings["Not Attending"] = array( + 0 => "", + 1 => "", + 2 => "", + 3 => "", +); +$a->strings["Undecided"] = array( + 0 => "", + 1 => "", + 2 => "", + 3 => "", +); +$a->strings["Forums"] = ""; +$a->strings["External link to forum"] = ""; $a->strings["view full size"] = "посмотреть в полный размер"; $a->strings["newer"] = "новее"; $a->strings["older"] = "старее"; @@ -1456,13 +1681,20 @@ $a->strings["prev"] = "пред."; $a->strings["first"] = "первый"; $a->strings["last"] = "последний"; $a->strings["next"] = "след."; +$a->strings["Loading more entries..."] = ""; +$a->strings["The end"] = ""; $a->strings["No contacts"] = "Нет контактов"; $a->strings["%d Contact"] = array( 0 => "%d контакт", 1 => "%d контактов", 2 => "%d контактов", + 3 => "%d контактов", ); +$a->strings["View Contacts"] = "Просмотр контактов"; +$a->strings["Full Text"] = "Контент"; +$a->strings["Tags"] = "Тэги"; $a->strings["poke"] = "poke"; +$a->strings["poked"] = ""; $a->strings["ping"] = "пинг"; $a->strings["pinged"] = "пингуется"; $a->strings["prod"] = ""; @@ -1493,29 +1725,10 @@ $a->strings["frustrated"] = ""; $a->strings["motivated"] = ""; $a->strings["relaxed"] = ""; $a->strings["surprised"] = ""; -$a->strings["Monday"] = "Понедельник"; -$a->strings["Tuesday"] = "Вторник"; -$a->strings["Wednesday"] = "Среда"; -$a->strings["Thursday"] = "Четверг"; -$a->strings["Friday"] = "Пятница"; -$a->strings["Saturday"] = "Суббота"; -$a->strings["Sunday"] = "Воскресенье"; -$a->strings["January"] = "Январь"; -$a->strings["February"] = "Февраль"; -$a->strings["March"] = "Март"; -$a->strings["April"] = "Апрель"; -$a->strings["May"] = "Май"; -$a->strings["June"] = "Июнь"; -$a->strings["July"] = "Июль"; -$a->strings["August"] = "Август"; -$a->strings["September"] = "Сентябрь"; -$a->strings["October"] = "Октябрь"; -$a->strings["November"] = "Ноябрь"; -$a->strings["December"] = "Декабрь"; $a->strings["bytes"] = "байт"; $a->strings["Click to open/close"] = "Нажмите, чтобы открыть / закрыть"; -$a->strings["default"] = "значение по умолчанию"; -$a->strings["Select an alternate language"] = "Выбор альтернативного языка"; +$a->strings["View on separate page"] = ""; +$a->strings["view on separate page"] = ""; $a->strings["activity"] = "активность"; $a->strings["post"] = "сообщение"; $a->strings["Item filed"] = ""; @@ -1524,8 +1737,6 @@ $a->strings["<a href=\"%1\$s\" target=\"_blank\">%2\$s</a> %3\$s"] = ""; $a->strings["<span><a href=\"%s\" target=\"_blank\">%s</a> wrote the following <a href=\"%s\" target=\"_blank\">post</a>"] = ""; $a->strings["$1 wrote:"] = "$1 написал:"; $a->strings["Encrypted content"] = "Зашифрованный контент"; -$a->strings["(no subject)"] = "(без темы)"; -$a->strings["noreply"] = "без ответа"; $a->strings["Cannot locate DNS info for database server '%s'"] = "Не могу найти информацию для DNS-сервера базы данных '%s'"; $a->strings["Unknown | Not categorised"] = "Неизвестно | Не определено"; $a->strings["Block immediately"] = "Блокировать немедленно"; @@ -1537,6 +1748,7 @@ $a->strings["Weekly"] = "Еженедельно"; $a->strings["Monthly"] = "Ежемесячно"; $a->strings["OStatus"] = "OStatus"; $a->strings["RSS/Atom"] = "RSS/Atom"; +$a->strings["Facebook"] = "Facebook"; $a->strings["Zot!"] = "Zot!"; $a->strings["LinkedIn"] = "LinkedIn"; $a->strings["XMPP/IM"] = "XMPP/IM"; @@ -1545,33 +1757,18 @@ $a->strings["Google+"] = "Google+"; $a->strings["pump.io"] = "pump.io"; $a->strings["Twitter"] = "Twitter"; $a->strings["Diaspora Connector"] = ""; -$a->strings["Statusnet"] = ""; +$a->strings["GNU Social"] = ""; $a->strings["App.net"] = ""; +$a->strings["Redmatrix"] = ""; $a->strings[" on Last.fm"] = "на Last.fm"; $a->strings["Starts:"] = "Начало:"; $a->strings["Finishes:"] = "Окончание:"; -$a->strings["j F, Y"] = "j F, Y"; -$a->strings["j F"] = "j F"; -$a->strings["Birthday:"] = "День рождения:"; -$a->strings["Age:"] = "Возраст:"; -$a->strings["for %1\$d %2\$s"] = ""; -$a->strings["Tags:"] = "Ключевые слова: "; -$a->strings["Religion:"] = "Религия:"; -$a->strings["Hobbies/Interests:"] = "Хобби / Интересы:"; -$a->strings["Contact information and Social Networks:"] = "Информация о контакте и социальных сетях:"; -$a->strings["Musical interests:"] = "Музыкальные интересы:"; -$a->strings["Books, literature:"] = "Книги, литература:"; -$a->strings["Television:"] = "Телевидение:"; -$a->strings["Film/dance/culture/entertainment:"] = "Кино / Танцы / Культура / Развлечения:"; -$a->strings["Love/Romance:"] = "Любовь / Романтика:"; -$a->strings["Work/employment:"] = "Работа / Занятость:"; -$a->strings["School/education:"] = "Школа / Образование:"; $a->strings["Click here to upgrade."] = "Нажмите для обновления."; $a->strings["This action exceeds the limits set by your subscription plan."] = "Это действие превышает лимиты, установленные вашим тарифным планом."; $a->strings["This action is not available under your subscription plan."] = "Это действие не доступно в соответствии с вашим планом подписки."; -$a->strings["End this session"] = "Конец этой сессии"; -$a->strings["Your posts and conversations"] = "Ваши сообщения и беседы"; -$a->strings["Your profile page"] = "Страница Вашего профиля"; +$a->strings["End this session"] = "Завершить эту сессию"; +$a->strings["Your posts and conversations"] = "Данные вашей учётной записи"; +$a->strings["Your profile page"] = "Информация о вас"; $a->strings["Your photos"] = "Ваши фотографии"; $a->strings["Your videos"] = ""; $a->strings["Your events"] = "Ваши события"; @@ -1588,9 +1785,9 @@ $a->strings["Conversations on this site"] = "Беседы на этом сайт $a->strings["Conversations on the network"] = ""; $a->strings["Directory"] = "Каталог"; $a->strings["People directory"] = "Каталог участников"; -$a->strings["Information"] = ""; +$a->strings["Information"] = "Информация"; $a->strings["Information about this friendica instance"] = ""; -$a->strings["Conversations from your friends"] = "Беседы с друзьями"; +$a->strings["Conversations from your friends"] = "Посты ваших друзей"; $a->strings["Network Reset"] = "Перезагрузка сети"; $a->strings["Load Network page with no filters"] = "Загрузить страницу сети без фильтров"; $a->strings["Friend Requests"] = "Запросы на добавление в список друзей"; @@ -1604,19 +1801,12 @@ $a->strings["Manage other pages"] = "Управление другими стр $a->strings["Account settings"] = "Настройки аккаунта"; $a->strings["Manage/Edit Profiles"] = "Управление/редактирование профилей"; $a->strings["Manage/edit friends and contacts"] = "Управление / редактирование друзей и контактов"; -$a->strings["Site setup and configuration"] = "Установка и конфигурация сайта"; +$a->strings["Site setup and configuration"] = "Конфигурация сайта"; $a->strings["Navigation"] = "Навигация"; $a->strings["Site map"] = "Карта сайта"; -$a->strings["User not found."] = "Пользователь не найден."; $a->strings["Daily posting limit of %d posts reached. The post was rejected."] = ""; $a->strings["Weekly posting limit of %d posts reached. The post was rejected."] = ""; $a->strings["Monthly posting limit of %d posts reached. The post was rejected."] = ""; -$a->strings["There is no status with this id."] = "Нет статуса с таким id."; -$a->strings["There is no conversation with this id."] = ""; -$a->strings["Invalid request."] = ""; -$a->strings["Invalid item."] = ""; -$a->strings["Invalid action. "] = ""; -$a->strings["DB error"] = ""; $a->strings["An invitation is required."] = "Требуется приглашение."; $a->strings["Invitation could not be verified."] = "Приглашение не может быть проверено."; $a->strings["Invalid OpenID url"] = "Неверный URL OpenID"; @@ -1627,17 +1817,20 @@ $a->strings["That doesn't appear to be your full (First Last) name."] = "Каж $a->strings["Your email domain is not among those allowed on this site."] = "Домен вашего адреса электронной почты не относится к числу разрешенных на этом сайте."; $a->strings["Not a valid email address."] = "Неверный адрес электронной почты."; $a->strings["Cannot use that email."] = "Нельзя использовать этот Email."; -$a->strings["Your \"nickname\" can only contain \"a-z\", \"0-9\", \"-\", and \"_\", and must also begin with a letter."] = "Ваш \"ник\" может содержать только \"a-z\", \"0-9\", \"-\", и \"_\", а также должен начинаться с буквы."; +$a->strings["Your \"nickname\" can only contain \"a-z\", \"0-9\" and \"_\"."] = ""; $a->strings["Nickname is already registered. Please choose another."] = "Такой ник уже зарегистрирован. Пожалуйста, выберите другой."; $a->strings["Nickname was once registered here and may not be re-used. Please choose another."] = "Ник уже зарегистрирован на этом сайте и не может быть изменён. Пожалуйста, выберите другой ник."; $a->strings["SERIOUS ERROR: Generation of security keys failed."] = "СЕРЬЕЗНАЯ ОШИБКА: генерация ключей безопасности не удалась."; $a->strings["An error occurred during registration. Please try again."] = "Ошибка при регистрации. Пожалуйста, попробуйте еще раз."; +$a->strings["default"] = "значение по умолчанию"; $a->strings["An error occurred creating your default profile. Please try again."] = "Ошибка создания вашего профиля. Пожалуйста, попробуйте еще раз."; $a->strings["Friends"] = "Друзья"; $a->strings["\n\t\tDear %1\$s,\n\t\t\tThank you for registering at %2\$s. Your account has been created.\n\t"] = ""; $a->strings["\n\t\tThe login details are as follows:\n\t\t\tSite Location:\t%3\$s\n\t\t\tLogin Name:\t%1\$s\n\t\t\tPassword:\t%5\$s\n\n\t\tYou may change your password from your account \"Settings\" page after logging\n\t\tin.\n\n\t\tPlease take a few moments to review the other account settings on that page.\n\n\t\tYou may also wish to add some basic information to your default profile\n\t\t(on the \"Profiles\" page) so that other people can easily find you.\n\n\t\tWe recommend setting your full name, adding a profile photo,\n\t\tadding some profile \"keywords\" (very useful in making new friends) - and\n\t\tperhaps what country you live in; if you do not wish to be more specific\n\t\tthan that.\n\n\t\tWe fully respect your right to privacy, and none of these items are necessary.\n\t\tIf you are new and do not know anybody here, they may help\n\t\tyou to make some new and interesting friends.\n\n\n\t\tThank you and welcome to %2\$s."] = ""; $a->strings["Sharing notification from Diaspora network"] = "Делиться уведомлениями из сети Diaspora"; $a->strings["Attachments:"] = "Вложения:"; +$a->strings["(no subject)"] = "(без темы)"; +$a->strings["noreply"] = "без ответа"; $a->strings["Do you really want to delete this item?"] = "Вы действительно хотите удалить этот элемент?"; $a->strings["Archives"] = "Архивы"; $a->strings["Male"] = "Мужчина"; @@ -1653,7 +1846,6 @@ $a->strings["Hermaphrodite"] = "Гермафродит"; $a->strings["Neuter"] = "Средний род"; $a->strings["Non-specific"] = "Не определен"; $a->strings["Other"] = "Другой"; -$a->strings["Undecided"] = "Не решено"; $a->strings["Males"] = "Мужчины"; $a->strings["Females"] = "Женщины"; $a->strings["Gay"] = "Гей"; @@ -1700,6 +1892,7 @@ $a->strings["Ask me"] = "Спросите меня"; $a->strings["Friendica Notification"] = "Friendica уведомления"; $a->strings["Thank You,"] = "Спасибо,"; $a->strings["%s Administrator"] = "%s администратор"; +$a->strings["%1\$s, %2\$s Administrator"] = ""; $a->strings["%s <!item_type!>"] = "%s <!item_type!>"; $a->strings["[Friendica:Notify] New mail received at %s"] = "[Friendica: Оповещение] Новое сообщение, пришедшее на %s"; $a->strings["%1\$s sent you a new private message at %2\$s."] = "%1\$s отправил вам новое личное сообщение на %2\$s."; @@ -1743,7 +1936,7 @@ $a->strings["Name:"] = "Имя:"; $a->strings["Photo:"] = "Фото:"; $a->strings["Please visit %s to approve or reject the suggestion."] = "Пожалуйста, посетите %s для подтверждения, или отказа запроса."; $a->strings["[Friendica:Notify] Connection accepted"] = ""; -$a->strings["'%1\$s' has acepted your connection request at %2\$s"] = ""; +$a->strings["'%1\$s' has accepted your connection request at %2\$s"] = ""; $a->strings["%2\$s has accepted your [url=%1\$s]connection request[/url]."] = ""; $a->strings["You are now mutual friends and may exchange status updates, photos, and email\n\twithout restriction."] = ""; $a->strings["Please visit %s if you wish to make any changes to this relationship."] = ""; @@ -1766,10 +1959,10 @@ $a->strings["%d contact not imported"] = array( 0 => "%d контакт не импортирован", 1 => "%d контакты не импортированы", 2 => "%d контакты не импортированы", + 3 => "%d контакты не импортированы", ); $a->strings["Done. You can now login with your username and password"] = "Завершено. Теперь вы можете войти с вашим логином и паролем"; $a->strings["toggle mobile"] = "мобильная версия"; -$a->strings["Theme settings"] = "Настройки темы"; $a->strings["Set resize level for images in posts and comments (width and height)"] = "Установить уровень изменения размера изображений в постах и комментариях (ширина и высота)"; $a->strings["Set font-size for posts and comments"] = "Установить шрифт-размер для постов и комментариев"; $a->strings["Set theme width"] = "Установить ширину темы"; @@ -1800,7 +1993,9 @@ $a->strings["Your personal photos"] = "Ваши личные фотографи $a->strings["Local Directory"] = "Локальный каталог"; $a->strings["Set zoomfactor for Earth Layers"] = "Установить масштаб карты"; $a->strings["Show/hide boxes at right-hand column:"] = "Показать/скрыть блоки в правой колонке:"; +$a->strings["Comma separated list of helper forums"] = ""; $a->strings["Set style"] = ""; +$a->strings["Quick Start"] = "Быстрый запуск"; $a->strings["greenzero"] = ""; $a->strings["purplezero"] = ""; $a->strings["easterbunny"] = ""; From f7537c9e90fa1b35618abd523d0d00f9658085a9 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 17 Feb 2016 00:01:24 +0100 Subject: [PATCH 110/273] Remove the baseurl where possible to avoid mixed content --- boot.php | 4 ++++ include/ForumManager.php | 10 +++++----- include/nav.php | 2 +- mod/network.php | 22 ++++++++++---------- object/Item.php | 2 +- view/templates/head.tpl | 42 +++++++++++++++++++-------------------- view/theme/vier/theme.php | 8 +++----- 7 files changed, 46 insertions(+), 44 deletions(-) diff --git a/boot.php b/boot.php index d9d7c62dc7..0217750136 100644 --- a/boot.php +++ b/boot.php @@ -920,6 +920,10 @@ class App { } function get_cached_avatar_image($avatar_image){ + // Just remove the base url. This avoid mixed content + $avatar_image = normalise_link($avatar_image); + $base = normalise_link($this->get_baseurl()); + $avatar_image = str_replace($base."/", "", $avatar_image); return $avatar_image; // The following code is deactivated. It doesn't seem to make any sense and it slows down the system. diff --git a/include/ForumManager.php b/include/ForumManager.php index 49417d1831..73eb511de6 100644 --- a/include/ForumManager.php +++ b/include/ForumManager.php @@ -89,18 +89,18 @@ class ForumManager { if(count($contacts)) { $id = 0; - +$a = get_app(); foreach($contacts as $contact) { $selected = (($cid == $contact['id']) ? ' forum-selected' : ''); $entry = array( - 'url' => z_root() . '/network?f=&cid=' . $contact['id'], - 'external_url' => z_root() . '/redir/' . $contact['id'], + 'url' => 'network?f=&cid=' . $contact['id'], + 'external_url' => 'redir/' . $contact['id'], 'name' => $contact['name'], 'cid' => $contact['id'], 'selected' => $selected, - 'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO), + 'micro' => $a->get_cached_avatar_image(proxy_url($contact['micro'], false, PROXY_SIZE_MICRO)), 'id' => ++$id, ); $entries[] = $entry; @@ -187,4 +187,4 @@ class ForumManager { return $r; } -} \ No newline at end of file +} diff --git a/include/nav.php b/include/nav.php index 6512d35609..fb7225af92 100644 --- a/include/nav.php +++ b/include/nav.php @@ -107,7 +107,7 @@ function nav_info(&$a) { if(($a->config['register_policy'] == REGISTER_OPEN) && (! local_user()) && (! remote_user())) $nav['register'] = array('register',t('Register'), "", t('Create an account')); - $help_url = $a->get_baseurl($ssl_state) . '/help'; + $help_url = 'help'; if(! get_config('system','hide_help')) $nav['help'] = array($help_url, t('Help'), "", t('Help and documentation')); diff --git a/mod/network.php b/mod/network.php index 0010a3d824..a9f369a894 100644 --- a/mod/network.php +++ b/mod/network.php @@ -149,10 +149,10 @@ function network_init(&$a) { $a->page['aside'] .= (feature_enabled(local_user(),'groups') ? group_side('network/0','network','standard',$group_id) : ''); $a->page['aside'] .= (feature_enabled(local_user(),'forumlist_widget') ? ForumManager::widget(local_user(),$cid) : ''); - $a->page['aside'] .= posted_date_widget($a->get_baseurl() . '/network',local_user(),false); - $a->page['aside'] .= networks_widget($a->get_baseurl(true) . '/network',(x($_GET, 'nets') ? $_GET['nets'] : '')); + $a->page['aside'] .= posted_date_widget('network',local_user(),false); + $a->page['aside'] .= networks_widget('network',(x($_GET, 'nets') ? $_GET['nets'] : '')); $a->page['aside'] .= saved_searches($search); - $a->page['aside'] .= fileas_widget($a->get_baseurl(true) . '/network',(x($_GET, 'file') ? $_GET['file'] : '')); + $a->page['aside'] .= fileas_widget('network',(x($_GET, 'file') ? $_GET['file'] : '')); } @@ -363,7 +363,7 @@ function network_content(&$a, $update = 0) { $tabs = array( array( 'label' => t('Commented Order'), - 'url' => $a->get_baseurl(true) . '/' . str_replace('/new', '', $cmd) . '?f=&order=comment' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : ''), + 'url' => str_replace('/new', '', $cmd) . '?f=&order=comment' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : ''), 'sel' => $all_active, 'title' => t('Sort by Comment Date'), 'id' => 'commented-order-tab', @@ -371,7 +371,7 @@ function network_content(&$a, $update = 0) { ), array( 'label' => t('Posted Order'), - 'url' => $a->get_baseurl(true) . '/' . str_replace('/new', '', $cmd) . '?f=&order=post' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : ''), + 'url' => str_replace('/new', '', $cmd) . '?f=&order=post' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : ''), 'sel' => $postord_active, 'title' => t('Sort by Post Date'), 'id' => 'posted-order-tab', @@ -382,7 +382,7 @@ function network_content(&$a, $update = 0) { if(feature_enabled(local_user(),'personal_tab')) { $tabs[] = array( 'label' => t('Personal'), - 'url' => $a->get_baseurl(true) . '/' . str_replace('/new', '', $cmd) . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&conv=1', + 'url' => str_replace('/new', '', $cmd) . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&conv=1', 'sel' => $conv_active, 'title' => t('Posts that mention or involve you'), 'id' => 'personal-tab', @@ -393,7 +393,7 @@ function network_content(&$a, $update = 0) { if(feature_enabled(local_user(),'new_tab')) { $tabs[] = array( 'label' => t('New'), - 'url' => $a->get_baseurl(true) . '/' . str_replace('/new', '', $cmd) . ($len_naked_cmd ? '/' : '') . 'new' . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : ''), + 'url' => str_replace('/new', '', $cmd) . ($len_naked_cmd ? '/' : '') . 'new' . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : ''), 'sel' => $new_active, 'title' => t('Activity Stream - by date'), 'id' => 'activitiy-by-date-tab', @@ -404,7 +404,7 @@ function network_content(&$a, $update = 0) { if(feature_enabled(local_user(),'link_tab')) { $tabs[] = array( 'label' => t('Shared Links'), - 'url' => $a->get_baseurl(true) . '/' . str_replace('/new', '', $cmd) . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&bmark=1', + 'url' => str_replace('/new', '', $cmd) . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&bmark=1', 'sel' => $bookmarked_active, 'title' => t('Interesting Links'), 'id' => 'shared-links-tab', @@ -415,7 +415,7 @@ function network_content(&$a, $update = 0) { if(feature_enabled(local_user(),'star_posts')) { $tabs[] = array( 'label' => t('Starred'), - 'url' => $a->get_baseurl(true) . '/' . str_replace('/new', '', $cmd) . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&star=1', + 'url' => str_replace('/new', '', $cmd) . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '/?f=') . '&star=1', 'sel' => $starred_active, 'title' => t('Favourite Posts'), 'id' => 'starred-posts-tab', @@ -547,7 +547,7 @@ function network_content(&$a, $update = 0) { if($update) killme(); notice( t('No such group') . EOL ); - goaway($a->get_baseurl(true) . '/network/0'); + goaway('network/0'); // NOTREACHED } @@ -611,7 +611,7 @@ function network_content(&$a, $update = 0) { } else { notice( t('Invalid contact.') . EOL); - goaway($a->get_baseurl(true) . '/network'); + goaway('network'); // NOTREACHED } } diff --git a/object/Item.php b/object/Item.php index 04c1a707e3..7b542c4724 100644 --- a/object/Item.php +++ b/object/Item.php @@ -705,7 +705,7 @@ class Item extends BaseObject { '$profile_uid' => $conv->get_profile_owner(), '$mylink' => $a->contact['url'], '$mytitle' => t('This is you'), - '$myphoto' => $a->contact['thumb'], + '$myphoto' => $a->get_cached_avatar_image($a->contact['thumb']), '$comment' => t('Comment'), '$submit' => t('Submit'), '$edbold' => t('Bold'), diff --git a/view/templates/head.tpl b/view/templates/head.tpl index 17c459c4d8..fdf9a7716e 100644 --- a/view/templates/head.tpl +++ b/view/templates/head.tpl @@ -2,17 +2,17 @@ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <base href="{{$baseurl}}/" /> <meta name="generator" content="{{$generator}}" /> -<link rel="stylesheet" href="{{$baseurl}}/view/global.css" type="text/css" media="all" /> -<link rel="stylesheet" href="{{$baseurl}}/library/colorbox/colorbox.css" type="text/css" media="screen" /> -<link rel="stylesheet" href="{{$baseurl}}/library/jgrowl/jquery.jgrowl.css" type="text/css" media="screen" /> -<link rel="stylesheet" href="{{$baseurl}}/library/datetimepicker/jquery.datetimepicker.css" type="text/css" media="screen" /> -<link rel="stylesheet" href="{{$baseurl}}/library/perfect-scrollbar/perfect-scrollbar.min.css" type="text/css" media="screen" /> +<link rel="stylesheet" href="view/global.css" type="text/css" media="all" /> +<link rel="stylesheet" href="library/colorbox/colorbox.css" type="text/css" media="screen" /> +<link rel="stylesheet" href="library/jgrowl/jquery.jgrowl.css" type="text/css" media="screen" /> +<link rel="stylesheet" href="library/datetimepicker/jquery.datetimepicker.css" type="text/css" media="screen" /> +<link rel="stylesheet" href="library/perfect-scrollbar/perfect-scrollbar.min.css" type="text/css" media="screen" /> <link rel="stylesheet" type="text/css" href="{{$stylesheet}}" media="all" /> <!-- -<link rel="shortcut icon" href="{{$baseurl}}/images/friendica-32.png" /> -<link rel="apple-touch-icon" href="{{$baseurl}}/images/friendica-128.png"/> +<link rel="shortcut icon" href="images/friendica-32.png" /> +<link rel="apple-touch-icon" href="images/friendica-128.png"/> --> <link rel="shortcut icon" href="{{$shortcut_icon}}" /> <link rel="apple-touch-icon" href="{{$touch_icon}}"/> @@ -28,20 +28,20 @@ <!--[if IE]> <script type="text/javascript" src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> -<script type="text/javascript" src="{{$baseurl}}/js/modernizr.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/js/jquery.js" ></script> -<!-- <script type="text/javascript" src="{{$baseurl}}/js/jquery-migrate.js" ></script>--> -<script type="text/javascript" src="{{$baseurl}}/js/jquery-migrate.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/js/jquery.textinputs.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/js/fk.autocomplete.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/library/colorbox/jquery.colorbox-min.js"></script> -<script type="text/javascript" src="{{$baseurl}}/library/jgrowl/jquery.jgrowl_minimized.js"></script> -<script type="text/javascript" src="{{$baseurl}}/library/datetimepicker/jquery.datetimepicker.js"></script> -<script type="text/javascript" src="{{$baseurl}}/library/tinymce/jscripts/tiny_mce/tiny_mce_src.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/library/perfect-scrollbar/perfect-scrollbar.jquery.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/js/acl.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/js/webtoolkit.base64.js" ></script> -<script type="text/javascript" src="{{$baseurl}}/js/main.js" ></script> +<script type="text/javascript" src="js/modernizr.js" ></script> +<script type="text/javascript" src="js/jquery.js" ></script> +<!-- <script type="text/javascript" src="js/jquery-migrate.js" ></script>--> +<script type="text/javascript" src="js/jquery-migrate.js" ></script> +<script type="text/javascript" src="js/jquery.textinputs.js" ></script> +<script type="text/javascript" src="js/fk.autocomplete.js" ></script> +<script type="text/javascript" src="library/colorbox/jquery.colorbox-min.js"></script> +<script type="text/javascript" src="library/jgrowl/jquery.jgrowl_minimized.js"></script> +<script type="text/javascript" src="library/datetimepicker/jquery.datetimepicker.js"></script> +<script type="text/javascript" src="library/tinymce/jscripts/tiny_mce/tiny_mce_src.js" ></script> +<script type="text/javascript" src="library/perfect-scrollbar/perfect-scrollbar.jquery.js" ></script> +<script type="text/javascript" src="js/acl.js" ></script> +<script type="text/javascript" src="js/webtoolkit.base64.js" ></script> +<script type="text/javascript" src="js/main.js" ></script> <script> var updateInterval = {{$update_interval}}; diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php index c2669f5a93..25954350f9 100644 --- a/view/theme/vier/theme.php +++ b/view/theme/vier/theme.php @@ -19,8 +19,6 @@ function vier_init(&$a) { set_template_engine($a, 'smarty3'); - $baseurl = $a->get_baseurl(); - $a->theme_info = array(); if ($a->argv[0].$a->argv[1] === "profile".$a->user['nickname'] or $a->argv[0] === "network" && local_user()) { @@ -160,7 +158,7 @@ function vier_community_info() { $entry = replace_macros($tpl,array( '$id' => $rr['id'], //'$profile_link' => zrl($rr['url']), - '$profile_link' => $a->get_baseurl().'/follow/?url='.urlencode($rr['url']), + '$profile_link' => 'follow/?url='.urlencode($rr['url']), '$photo' => proxy_url($rr['photo'], false, PROXY_SIZE_MICRO), '$alt_text' => $rr['name'], )); @@ -186,7 +184,7 @@ function vier_community_info() { $aside['$lastusers_items'] = array(); foreach($r as $rr) { - $profile_link = $a->get_baseurl() . '/profile/' . ((strlen($rr['nickname'])) ? $rr['nickname'] : $rr['profile_uid']); + $profile_link = 'profile/' . ((strlen($rr['nickname'])) ? $rr['nickname'] : $rr['profile_uid']); $entry = replace_macros($tpl,array( '$id' => $rr['id'], '$profile_link' => $profile_link, @@ -207,7 +205,7 @@ function vier_community_info() { $nv['suggest'] = Array('suggest', t('Friend Suggestions'), "", ""); $nv['invite'] = Array('invite', t('Invite Friends'), "", ""); - $nv['search'] = '<form name="simple_bar" method="get" action="'.$a->get_baseurl().'/dirfind"> + $nv['search'] = '<form name="simple_bar" method="get" action="dirfind"> <span class="sbox_l"></span> <span class="sbox"> <input type="text" name="search" size="13" maxlength="50"> From b21275ee9c59a9f9d184b4f1f4cb7421b04e0f1c Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Wed, 17 Feb 2016 08:00:38 +0100 Subject: [PATCH 111/273] handle exception with non-existing addon directory --- util/make_credits.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/util/make_credits.py b/util/make_credits.py index 404b059133..eacb8707f4 100755 --- a/util/make_credits.py +++ b/util/make_credits.py @@ -46,17 +46,20 @@ for i in c: n1 = len(contributors) print(' > found %d contributors' % n1) # get the contributors to the addons -os.chdir(path+'/addon') -# get the contributors -print('> getting contributors to the addons') -p = subprocess.Popen(['git', 'shortlog', '--no-merges', '-s'], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) -c = iter(p.stdout.readline, b'') -for i in c: - name = i.decode().split('\t')[1].split('\n')[0] - if not name in contributors and name not in dontinclude: - contributors.append(name) +try: + os.chdir(path+'/addon') + # get the contributors + print('> getting contributors to the addons') + p = subprocess.Popen(['git', 'shortlog', '--no-merges', '-s'], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + c = iter(p.stdout.readline, b'') + for i in c: + name = i.decode().split('\t')[1].split('\n')[0] + if not name in contributors and name not in dontinclude: + contributors.append(name) +except FileNotFoundError: + print(' > no addon directory found ( THE LIST OF CONTRIBUTORS WILL BE INCOMPLETE )') n2 = len(contributors) print(' > found %d new contributors' % (n2-n1)) print('> total of %d contributors to the repositories of friendica' % n2) From c5b724828a12a215d66e8086ed0ffdabfad5e928 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 17 Feb 2016 08:08:28 +0100 Subject: [PATCH 112/273] Just some more removed baseurls ... --- boot.php | 23 +++++++++++++++++++---- include/ForumManager.php | 4 ++-- include/conversation.php | 2 +- include/identity.php | 6 +++--- include/nav.php | 2 +- index.php | 6 +++--- mod/content.php | 4 ++-- mod/directory.php | 4 ++-- mod/profiles.php | 28 ++++++++++------------------ object/Item.php | 4 ++-- view/theme/vier/theme.php | 2 +- 11 files changed, 46 insertions(+), 39 deletions(-) diff --git a/boot.php b/boot.php index 0217750136..851008aef5 100644 --- a/boot.php +++ b/boot.php @@ -920,10 +920,6 @@ class App { } function get_cached_avatar_image($avatar_image){ - // Just remove the base url. This avoid mixed content - $avatar_image = normalise_link($avatar_image); - $base = normalise_link($this->get_baseurl()); - $avatar_image = str_replace($base."/", "", $avatar_image); return $avatar_image; // The following code is deactivated. It doesn't seem to make any sense and it slows down the system. @@ -951,6 +947,25 @@ class App { } + /** + * @brief Removes the baseurl from an url. This avoids some mixed content problems. + * + * @param string $url + * + * @return string The cleaned url + */ + function remove_baseurl($url){ + + // Is the function called statically? + if (!is_object($this)) + return(self::$a->remove_baseurl($ssl)); + + $url = normalise_link($url); + $base = normalise_link($this->get_baseurl()); + $url = str_replace($base."/", "", $url); + return $url; + } + /** * @brief Register template engine class * diff --git a/include/ForumManager.php b/include/ForumManager.php index 73eb511de6..6fede0204d 100644 --- a/include/ForumManager.php +++ b/include/ForumManager.php @@ -89,7 +89,7 @@ class ForumManager { if(count($contacts)) { $id = 0; -$a = get_app(); + foreach($contacts as $contact) { $selected = (($cid == $contact['id']) ? ' forum-selected' : ''); @@ -100,7 +100,7 @@ $a = get_app(); 'name' => $contact['name'], 'cid' => $contact['id'], 'selected' => $selected, - 'micro' => $a->get_cached_avatar_image(proxy_url($contact['micro'], false, PROXY_SIZE_MICRO)), + 'micro' => App::remove_baseurl(proxy_url($contact['micro'], false, PROXY_SIZE_MICRO)), 'id' => ++$id, ); $entries[] = $entry; diff --git a/include/conversation.php b/include/conversation.php index 6c33be84fb..53a7b3d1ed 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -614,7 +614,7 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { if(($normalised != 'mailbox') && (x($a->contacts[$normalised]))) $profile_avatar = $a->contacts[$normalised]['thumb']; else - $profile_avatar = ((strlen($item['author-avatar'])) ? $a->get_cached_avatar_image($item['author-avatar']) : $item['thumb']); + $profile_avatar = $a->remove_baseurl(((strlen($item['author-avatar'])) ? $item['author-avatar'] : $item['thumb'])); $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); call_hooks('render_location',$locate); diff --git a/include/identity.php b/include/identity.php index ec66225d0f..aba69bae49 100644 --- a/include/identity.php +++ b/include/identity.php @@ -332,9 +332,9 @@ function profile_sidebar($profile, $block = 0) { 'fullname' => $profile['name'], 'firstname' => $firstname, 'lastname' => $lastname, - 'photo300' => $a->get_cached_avatar_image($a->get_baseurl() . '/photo/custom/300/' . $profile['uid'] . '.jpg'), - 'photo100' => $a->get_cached_avatar_image($a->get_baseurl() . '/photo/custom/100/' . $profile['uid'] . '.jpg'), - 'photo50' => $a->get_cached_avatar_image($a->get_baseurl() . '/photo/custom/50/' . $profile['uid'] . '.jpg'), + 'photo300' => $a->get_baseurl() . '/photo/custom/300/' . $profile['uid'] . '.jpg', + 'photo100' => $a->get_baseurl() . '/photo/custom/100/' . $profile['uid'] . '.jpg', + 'photo50' => $a->get_baseurl() . '/photo/custom/50/' . $profile['uid'] . '.jpg', ); if (!$block){ diff --git a/include/nav.php b/include/nav.php index fb7225af92..0fa671a27d 100644 --- a/include/nav.php +++ b/include/nav.php @@ -82,7 +82,7 @@ function nav_info(&$a) { // user info $r = q("SELECT micro FROM contact WHERE uid=%d AND self=1", intval($a->user['uid'])); $userinfo = array( - 'icon' => (count($r) ? $a->get_cached_avatar_image($r[0]['micro']) : $a->get_baseurl($ssl_state)."/images/person-48.jpg"), + 'icon' => (count($r) ? $a->remove_baseurl($r[0]['micro']) : "images/person-48.jpg"), 'name' => $a->user['username'], ); diff --git a/index.php b/index.php index 8471735d01..e364389b2c 100644 --- a/index.php +++ b/index.php @@ -371,7 +371,7 @@ $a->init_page_end(); if(x($_SESSION,'visitor_home')) $homebase = $_SESSION['visitor_home']; elseif(local_user()) - $homebase = $a->get_baseurl() . '/profile/' . $a->user['nickname']; + $homebase = 'profile/' . $a->user['nickname']; if(isset($homebase)) $a->page['content'] .= '<script>var homebase="' . $homebase . '" ; </script>'; @@ -423,10 +423,10 @@ if($a->module != 'install' && $a->module != 'maintenance') { if($a->is_mobile || $a->is_tablet) { if(isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) { - $link = $a->get_baseurl() . '/toggle_mobile?address=' . curPageURL(); + $link = 'toggle_mobile?address=' . curPageURL(); } else { - $link = $a->get_baseurl() . '/toggle_mobile?off=1&address=' . curPageURL(); + $link = 'toggle_mobile?off=1&address=' . curPageURL(); } $a->page['footer'] = replace_macros(get_markup_template("toggle_mobile_footer.tpl"), array( '$toggle_link' => $link, diff --git a/mod/content.php b/mod/content.php index c5a5556116..c2b1546bf1 100644 --- a/mod/content.php +++ b/mod/content.php @@ -420,7 +420,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if(($normalised != 'mailbox') && (x($a->contacts[$normalised]))) $profile_avatar = $a->contacts[$normalised]['thumb']; else - $profile_avatar = ((strlen($item['author-avatar'])) ? $a->get_cached_avatar_image($item['author-avatar']) : $item['thumb']); + $profile_avatar = $a->remove_baseurl(((strlen($item['author-avatar'])) ? $item['author-avatar'] : $item['thumb'])); $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); call_hooks('render_location',$locate); @@ -791,7 +791,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { if(($normalised != 'mailbox') && (x($a->contacts,$normalised))) $profile_avatar = $a->contacts[$normalised]['thumb']; else - $profile_avatar = (((strlen($item['author-avatar'])) && $diff_author) ? $item['author-avatar'] : $a->get_cached_avatar_image($thumb)); + $profile_avatar = $a->remove_baseurl(((strlen($item['author-avatar']) && $diff_author) ? $item['author-avatar'] : $thumb)); $like = ((x($alike,$item['uri'])) ? format_like($alike[$item['uri']],$alike[$item['uri'] . '-l'],'like',$item['uri']) : ''); $dislike = ((x($dlike,$item['uri'])) ? format_like($dlike[$item['uri']],$dlike[$item['uri'] . '-l'],'dislike',$item['uri']) : ''); diff --git a/mod/directory.php b/mod/directory.php index 294a55585d..be09dd37f6 100644 --- a/mod/directory.php +++ b/mod/directory.php @@ -158,14 +158,14 @@ function directory_content(&$a) { else { $location_e = $location; } - + $photo_menu = array(array(t("View Profile"), zrl($profile_link))); $entry = array( 'id' => $rr['id'], 'url' => $profile_link, 'itemurl' => $itemurl, - 'thumb' => proxy_url($a->get_cached_avatar_image($rr[$photo]), false, PROXY_SIZE_THUMB), + 'thumb' => proxy_url($rr[$photo], false, PROXY_SIZE_THUMB), 'img_hover' => $rr['name'], 'name' => $rr['name'], 'details' => $details, diff --git a/mod/profiles.php b/mod/profiles.php index 5c372de8ee..0b8261422f 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -16,7 +16,7 @@ function profiles_init(&$a) { ); if(! count($r)) { notice( t('Profile not found.') . EOL); - goaway($a->get_baseurl(true) . '/profiles'); + goaway('profiles'); return; // NOTREACHED } @@ -34,9 +34,9 @@ function profiles_init(&$a) { intval(local_user()) ); if($r) - info( t('Profile deleted.') . EOL); + info(t('Profile deleted.').EOL); - goaway($a->get_baseurl(true) . '/profiles'); + goaway('profiles'); return; // NOTREACHED } @@ -73,9 +73,9 @@ function profiles_init(&$a) { info( t('New profile created.') . EOL); if(count($r3) == 1) - goaway($a->get_baseurl(true) . '/profiles/' . $r3[0]['id']); + goaway('profiles/'.$r3[0]['id']); - goaway($a->get_baseurl(true) . '/profiles'); + goaway('profiles'); } if(($a->argc > 2) && ($a->argv[1] === 'clone')) { @@ -116,9 +116,9 @@ function profiles_init(&$a) { ); info( t('New profile created.') . EOL); if(count($r3) == 1) - goaway($a->get_baseurl(true) . '/profiles/' . $r3[0]['id']); + goaway('profiles/'.$r3[0]['id']); - goaway($a->get_baseurl(true) . '/profiles'); + goaway('profiles'); return; // NOTREACHED } @@ -582,15 +582,7 @@ function profile_activity($changed, $value) { $i = item_store($arr); if($i) { - - // give it a permanent link - //q("update item set plink = '%s' where id = %d", - // dbesc($a->get_baseurl() . '/display/' . $a->user['nickname'] . '/' . $i), - // intval($i) - //); - proc_run('php',"include/notifier.php","activity","$i"); - } } @@ -786,7 +778,7 @@ function profiles_content(&$a) { ); if(count($r)){ //Go to the default profile. - goaway($a->get_baseurl(true) . '/profiles/'.$r[0]['id']); + goaway('profiles/'.$r[0]['id']); } } @@ -807,12 +799,12 @@ function profiles_content(&$a) { foreach($r as $rr) { $o .= replace_macros($tpl, array( - '$photo' => $a->get_cached_avatar_image($rr['thumb']), + '$photo' => $a->remove_baseurl($rr['thumb']), '$id' => $rr['id'], '$alt' => t('Profile Image'), '$profile_name' => $rr['profile-name'], '$visible' => (($rr['is-default']) ? '<strong>' . t('visible to everybody') . '</strong>' - : '<a href="' . $a->get_baseurl(true) . '/profperm/' . $rr['id'] . '" />' . t('Edit visibility') . '</a>') + : '<a href="'.'profperm/'.$rr['id'].'" />' . t('Edit visibility') . '</a>') )); } } diff --git a/object/Item.php b/object/Item.php index 7b542c4724..9daf44648e 100644 --- a/object/Item.php +++ b/object/Item.php @@ -154,7 +154,7 @@ class Item extends BaseObject { if(($normalised != 'mailbox') && (x($a->contacts,$normalised))) $profile_avatar = $a->contacts[$normalised]['thumb']; else - $profile_avatar = (((strlen($item['author-avatar'])) && $diff_author) ? $item['author-avatar'] : $a->get_cached_avatar_image($this->get_data_value('thumb'))); + $profile_avatar = (((strlen($item['author-avatar'])) && $diff_author) ? $item['author-avatar'] : $a->remove_baseurl($this->get_data_value('thumb'))); $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); call_hooks('render_location',$locate); @@ -705,7 +705,7 @@ class Item extends BaseObject { '$profile_uid' => $conv->get_profile_owner(), '$mylink' => $a->contact['url'], '$mytitle' => t('This is you'), - '$myphoto' => $a->get_cached_avatar_image($a->contact['thumb']), + '$myphoto' => $a->remove_baseurl($a->contact['thumb']), '$comment' => t('Comment'), '$submit' => t('Submit'), '$edbold' => t('Bold'), diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php index 25954350f9..f33a9178ac 100644 --- a/view/theme/vier/theme.php +++ b/view/theme/vier/theme.php @@ -188,7 +188,7 @@ function vier_community_info() { $entry = replace_macros($tpl,array( '$id' => $rr['id'], '$profile_link' => $profile_link, - '$photo' => $a->get_cached_avatar_image($rr['thumb']), + '$photo' => $a->remove_baseurl($rr['thumb']), '$alt_text' => $rr['name'])); $aside['$lastusers_items'][] = $entry; } From 8af8567567e67af97871d12333d6d55c79c5a67d Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 17 Feb 2016 12:18:36 +0100 Subject: [PATCH 113/273] Some more ... --- boot.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/boot.php b/boot.php index 851008aef5..69440e69d2 100644 --- a/boot.php +++ b/boot.php @@ -864,11 +864,11 @@ class App { $shortcut_icon = get_config("system", "shortcut_icon"); if ($shortcut_icon == "") - $shortcut_icon = $this->get_baseurl()."/images/friendica-32.png"; + $shortcut_icon = "images/friendica-32.png"; $touch_icon = get_config("system", "touch_icon"); if ($touch_icon == "") - $touch_icon = $this->get_baseurl()."/images/friendica-128.png"; + $touch_icon = "images/friendica-128.png"; $tpl = get_markup_template('head.tpl'); $this->page['htmlhead'] = replace_macros($tpl,array( @@ -1447,7 +1447,7 @@ function login($register = false, $hiddens=false) { $noid = get_config('system','no_openid'); - $dest_url = $a->get_baseurl(true) . '/' . $a->query_string; + $dest_url = $a->query_string; if(local_user()) { $tpl = get_markup_template("logout.tpl"); @@ -1766,9 +1766,9 @@ function current_theme_url() { $opts = (($a->profile_uid) ? '?f=&puid=' . $a->profile_uid : ''); if (file_exists('view/theme/' . $t . '/style.php')) - return($a->get_baseurl() . '/view/theme/' . $t . '/style.pcss' . $opts); + return('view/theme/'.$t.'/style.pcss'.$opts); - return($a->get_baseurl() . '/view/theme/' . $t . '/style.css'); + return('view/theme/'.$t.'/style.css'); } function feed_birthday($uid,$tz) { From e22d980e91c846bb9f3b3f09de4b1b830e256a13 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Wed, 17 Feb 2016 15:13:45 +0100 Subject: [PATCH 114/273] IT update to the strings --- view/it/messages.po | 14 +++++++------- view/it/strings.php | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/view/it/messages.po b/view/it/messages.po index b2b88bc72f..a9df298ca9 100644 --- a/view/it/messages.po +++ b/view/it/messages.po @@ -16,7 +16,7 @@ msgstr "" "Project-Id-Version: friendica\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-01-24 06:49+0100\n" -"PO-Revision-Date: 2016-01-30 08:43+0000\n" +"PO-Revision-Date: 2016-02-16 10:29+0000\n" "Last-Translator: Sandro Santilli <strk@keybit.net>\n" "Language-Team: Italian (http://www.transifex.com/Friendica/friendica/language/it/)\n" "MIME-Version: 1.0\n" @@ -41,8 +41,8 @@ msgstr "Forum" #, php-format msgid "%d contact edited." msgid_plural "%d contacts edited." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d contatto modificato." +msgstr[1] "%d contatti modificati" #: mod/contacts.php:159 mod/contacts.php:383 msgid "Could not access contact record." @@ -887,7 +887,7 @@ msgstr "Rimuovi" #: mod/ostatus_subscribe.php:14 msgid "Subscribing to OStatus contacts" -msgstr "" +msgstr "Iscrizione a contatti OStatus" #: mod/ostatus_subscribe.php:25 msgid "No contact provided." @@ -1943,7 +1943,7 @@ msgstr "Ispeziona Coda di invio" #: mod/admin.php:163 mod/admin.php:354 msgid "Federation Statistics" -msgstr "" +msgstr "Statistiche sulla Federazione" #: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1792 msgid "Logs" @@ -1951,7 +1951,7 @@ msgstr "Log" #: mod/admin.php:178 mod/admin.php:1859 msgid "View Logs" -msgstr "" +msgstr "Vedi i log" #: mod/admin.php:179 msgid "probe address" @@ -1982,7 +1982,7 @@ msgid "" "This page offers you some numbers to the known part of the federated social " "network your Friendica node is part of. These numbers are not complete but " "only reflect the part of the network your node is aware of." -msgstr "" +msgstr "Questa pagina offre alcuni numeri riguardo la porzione del social network federato di cui il tuo nodo Friendica fa parte. Questi numeri non sono completi ma riflettono esclusivamente la porzione di rete di cui il tuo nodo e' a conoscenza." #: mod/admin.php:348 msgid "" diff --git a/view/it/strings.php b/view/it/strings.php index 606ae1e16c..131e03080b 100644 --- a/view/it/strings.php +++ b/view/it/strings.php @@ -8,8 +8,8 @@ function string_plural_select_it($n){ $a->strings["Network:"] = "Rete:"; $a->strings["Forum"] = "Forum"; $a->strings["%d contact edited."] = array( - 0 => "", - 1 => "", + 0 => "%d contatto modificato.", + 1 => "%d contatti modificati", ); $a->strings["Could not access contact record."] = "Non è possibile accedere al contatto."; $a->strings["Could not locate selected profile."] = "Non riesco a trovare il profilo selezionato."; @@ -186,7 +186,7 @@ $a->strings["Tag removed"] = "Tag rimosso"; $a->strings["Remove Item Tag"] = "Rimuovi il tag"; $a->strings["Select a tag to remove: "] = "Seleziona un tag da rimuovere: "; $a->strings["Remove"] = "Rimuovi"; -$a->strings["Subscribing to OStatus contacts"] = ""; +$a->strings["Subscribing to OStatus contacts"] = "Iscrizione a contatti OStatus"; $a->strings["No contact provided."] = "Nessun contatto disponibile."; $a->strings["Couldn't fetch information for contact."] = "Non è stato possibile recuperare le informazioni del contatto."; $a->strings["Couldn't fetch friends for contact."] = "Non è stato possibile recuperare gli amici del contatto."; @@ -419,16 +419,16 @@ $a->strings["Themes"] = "Temi"; $a->strings["Additional features"] = "Funzionalità aggiuntive"; $a->strings["DB updates"] = "Aggiornamenti Database"; $a->strings["Inspect Queue"] = "Ispeziona Coda di invio"; -$a->strings["Federation Statistics"] = ""; +$a->strings["Federation Statistics"] = "Statistiche sulla Federazione"; $a->strings["Logs"] = "Log"; -$a->strings["View Logs"] = ""; +$a->strings["View Logs"] = "Vedi i log"; $a->strings["probe address"] = "controlla indirizzo"; $a->strings["check webfinger"] = "verifica webfinger"; $a->strings["Admin"] = "Amministrazione"; $a->strings["Plugin Features"] = "Impostazioni Plugins"; $a->strings["diagnostics"] = "diagnostiche"; $a->strings["User registrations waiting for confirmation"] = "Utenti registrati in attesa di conferma"; -$a->strings["This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."] = ""; +$a->strings["This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."] = "Questa pagina offre alcuni numeri riguardo la porzione del social network federato di cui il tuo nodo Friendica fa parte. Questi numeri non sono completi ma riflettono esclusivamente la porzione di rete di cui il tuo nodo e' a conoscenza."; $a->strings["The <em>Auto Discovered Contact Directory</em> feature is not enabled, it will improve the data displayed here."] = ""; $a->strings["Administration"] = "Amministrazione"; $a->strings["Currently this node is aware of %d nodes from the following platforms:"] = ""; From 7d4c99ebbc0ab4bd548fdd89680d5e75af4f85b9 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 17 Feb 2016 23:47:32 +0100 Subject: [PATCH 115/273] "get_baseurl" and "z_root" are disappearing ... --- boot.php | 2 +- include/conversation.php | 22 ++--- include/text.php | 24 ++--- mod/admin.php | 186 ++++++++++++++++++------------------- mod/common.php | 2 +- mod/contacts.php | 52 +++++------ mod/content.php | 2 +- mod/directory.php | 2 +- mod/message.php | 18 ++-- mod/notifications.php | 16 ++-- mod/photos.php | 78 +++++++--------- mod/search.php | 2 +- mod/settings.php | 26 +++--- mod/uexport.php | 62 ++----------- object/Item.php | 10 +- view/templates/uexport.tpl | 18 ++-- view/theme/vier/style.css | 4 +- view/theme/vier/theme.php | 6 +- 18 files changed, 235 insertions(+), 297 deletions(-) diff --git a/boot.php b/boot.php index df4a1fdea2..d82669f231 100644 --- a/boot.php +++ b/boot.php @@ -949,7 +949,7 @@ class App { // Is the function called statically? if (!is_object($this)) - return(self::$a->remove_baseurl($ssl)); + return(self::$a->remove_baseurl($url)); $url = normalise_link($url); $base = normalise_link($this->get_baseurl()); diff --git a/include/conversation.php b/include/conversation.php index 53a7b3d1ed..a52502ec39 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -707,8 +707,8 @@ function conversation(&$a, $items, $mode, $update, $preview = false) { 'like' => '', 'dislike' => '', 'comment' => '', - //'conv' => (($preview) ? '' : array('href'=> $a->get_baseurl($ssl_state) . '/display/' . $nickname . '/' . $item['id'], 'title'=> t('View in context'))), - 'conv' => (($preview) ? '' : array('href'=> $a->get_baseurl($ssl_state) . '/display/'.$item['guid'], 'title'=> t('View in context'))), + //'conv' => (($preview) ? '' : array('href'=> 'display/' . $nickname . '/' . $item['id'], 'title'=> t('View in context'))), + 'conv' => (($preview) ? '' : array('href'=> 'display/'.$item['guid'], 'title'=> t('View in context'))), 'previewing' => $previewing, 'wait' => t('Please wait'), 'thread_level' => 1, @@ -868,7 +868,7 @@ function item_photo_menu($item){ $status_link = $profile_link . "?url=status"; $photos_link = $profile_link . "?url=photos"; $profile_link = $profile_link . "?url=profile"; - $pm_url = $a->get_baseurl($ssl_state) . '/message/new/' . $cid; + $pm_url = 'message/new/' . $cid; $zurl = ''; } else { @@ -882,23 +882,23 @@ function item_photo_menu($item){ $cid = $r[0]["id"]; if ($r[0]["network"] == NETWORK_DIASPORA) - $pm_url = $a->get_baseurl($ssl_state) . '/message/new/' . $cid; + $pm_url = 'message/new/' . $cid; } else $cid = 0; } } if(($cid) && (! $item['self'])) { - $poke_link = $a->get_baseurl($ssl_state) . '/poke/?f=&c=' . $cid; - $contact_url = $a->get_baseurl($ssl_state) . '/contacts/' . $cid; - $posts_link = $a->get_baseurl($ssl_state) . '/contacts/' . $cid . '/posts'; + $poke_link = 'poke/?f=&c=' . $cid; + $contact_url = 'contacts/' . $cid; + $posts_link = 'contacts/' . $cid . '/posts'; $clean_url = normalise_link($item['author-link']); if((local_user()) && (local_user() == $item['uid'])) { if(isset($a->contacts) && x($a->contacts,$clean_url)) { if($a->contacts[$clean_url]['network'] === NETWORK_DIASPORA) { - $pm_url = $a->get_baseurl($ssl_state) . '/message/new/' . $cid; + $pm_url = 'message/new/' . $cid; } } } @@ -921,7 +921,7 @@ function item_photo_menu($item){ if ((($cid == 0) OR ($a->contacts[$clean_url]['rel'] == CONTACT_IS_FOLLOWER)) AND in_array($item['network'], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA))) - $menu[t("Connect/Follow")] = $a->get_baseurl($ssl_state)."/follow?url=".urlencode($item['author-link']); + $menu[t("Connect/Follow")] = "follow?url=".urlencode($item['author-link']); } else $menu = array(t("View Profile") => $item['author-link']); @@ -980,7 +980,7 @@ function builtin_activity_puller($item, &$conv_responses) { if((activity_match($item['verb'], $verb)) && ($item['id'] != $item['parent'])) { $url = $item['author-link']; if((local_user()) && (local_user() == $item['uid']) && ($item['network'] === NETWORK_DFRN) && (! $item['self']) && (link_compare($item['author-link'],$item['url']))) { - $url = z_root(true) . '/redir/' . $item['contact-id']; + $url = 'redir/' . $item['contact-id']; $sparkle = ' class="sparkle" '; } else @@ -1178,7 +1178,7 @@ function status_editor($a,$x, $notes_cid = 0, $popup=false) { $o .= replace_macros($tpl,array( '$return_path' => $query_str, - '$action' => $a->get_baseurl(true) . '/item', + '$action' => 'item', '$share' => (x($x,'button') ? $x['button'] : t('Share')), '$upload' => t('Upload photo'), '$shortupload' => t('upload photo'), diff --git a/include/text.php b/include/text.php index c7681a4d58..07524e851d 100644 --- a/include/text.php +++ b/include/text.php @@ -285,7 +285,7 @@ function paginate_data(&$a, $count=null) { if (($a->page_offset != "") AND !preg_match('/[?&].offset=/', $stripped)) $stripped .= "&offset=".urlencode($a->page_offset); - $url = z_root() . '/' . $stripped; + $url = $stripped; $data = array(); function _l(&$d, $name, $url, $text, $class="") { @@ -923,7 +923,7 @@ function micropro($contact, $redirect = false, $class = '', $textmode = false) { if($redirect) { $a = get_app(); - $redirect_url = z_root() . '/redir/' . $contact['id']; + $redirect_url = 'redir/' . $contact['id']; if(local_user() && ($contact['uid'] == local_user()) && ($contact['network'] === NETWORK_DFRN)) { $redir = true; $url = $redirect_url; @@ -964,13 +964,13 @@ if(! function_exists('search')) { * @param string $url search url * @param boolean $savedsearch show save search button */ -function search($s,$id='search-box',$url='/search',$save = false, $aside = true) { +function search($s,$id='search-box',$url='search',$save = false, $aside = true) { $a = get_app(); $values = array( '$s' => $s, '$id' => $id, - '$action_url' => $a->get_baseurl((stristr($url,'network')) ? true : false) . $url, + '$action_url' => $url, '$search_label' => t('Search'), '$save_label' => t('Save'), '$savedsearch' => feature_enabled(local_user(),'savedsearch'), @@ -1305,7 +1305,7 @@ function redir_private_images($a, &$item) { if((local_user() == $item['uid']) && ($item['private'] != 0) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) { //logger("redir_private_images: redir"); - $img_url = z_root() . '/redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link']; + $img_url = 'redir?f=1&quiet=1&url=' . $mtch[1] . '&conurl=' . $item['author-link']; $item['body'] = str_replace($mtch[0], "[img]".$img_url."[/img]", $item['body']); } } @@ -1421,7 +1421,7 @@ function prepare_body(&$item,$attach = false, $preview = false) { $mime = $mtch[3]; if((local_user() == $item['uid']) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == NETWORK_DFRN)) - $the_url = z_root() . '/redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1]; + $the_url = 'redir/' . $item['contact-id'] . '?f=1&url=' . $mtch[1]; else $the_url = $mtch[1]; @@ -1596,7 +1596,7 @@ function get_cats_and_terms($item) { $categories[] = array( 'name' => xmlify(file_tag_decode($mtch[1])), 'url' => "#", - 'removeurl' => ((local_user() == $item['uid'])?z_root() . '/filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""), + 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&cat=' . xmlify(file_tag_decode($mtch[1])):""), 'first' => $first, 'last' => false ); @@ -1614,7 +1614,7 @@ function get_cats_and_terms($item) { $folders[] = array( 'name' => xmlify(file_tag_decode($mtch[1])), 'url' => "#", - 'removeurl' => ((local_user() == $item['uid'])?z_root() . '/filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""), + 'removeurl' => ((local_user() == $item['uid'])?'filerm/' . $item['id'] . '?f=&term=' . xmlify(file_tag_decode($mtch[1])):""), 'first' => $first, 'last' => false ); @@ -1639,15 +1639,15 @@ function get_plink($item) { if ($a->user['nickname'] != "") { $ret = array( - //'href' => z_root()."/display/".$a->user['nickname']."/".$item['id'], - 'href' => z_root()."/display/".$item['guid'], - 'orig' => z_root()."/display/".$item['guid'], + //'href' => "display/".$a->user['nickname']."/".$item['id'], + 'href' => "display/".$item['guid'], + 'orig' => "display/".$item['guid'], 'title' => t('View on separate page'), 'orig_title' => t('view on separate page'), ); if (x($item,'plink')) { - $ret["href"] = $item['plink']; + $ret["href"] = $a->remove_baseurl($item['plink']); $ret["title"] = t('link to source'); } diff --git a/mod/admin.php b/mod/admin.php index 7f9000807b..ecd08bbe00 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -55,13 +55,13 @@ function admin_post(&$a){ $func($a); } } - goaway($a->get_baseurl(true) . '/admin/plugins/' . $a->argv[2] ); + goaway('admin/plugins/'.$a->argv[2]); return; // NOTREACHED break; case 'themes': if($a->argc < 2) { if(is_ajax()) return; - goaway($a->get_baseurl(true) . '/admin/' ); + goaway('admin/'); return; } @@ -92,7 +92,7 @@ function admin_post(&$a){ info(t('Theme settings updated.')); if(is_ajax()) return; - goaway($a->get_baseurl(true) . '/admin/themes/' . $theme ); + goaway('admin/themes/'.$theme); return; break; case 'features': @@ -107,7 +107,7 @@ function admin_post(&$a){ } } - goaway($a->get_baseurl(true) . '/admin' ); + goaway('admin'); return; // NOTREACHED } @@ -150,17 +150,17 @@ function admin_content(&$a) { * Side bar links */ $aside_tools = array(); - // array( url, name, extra css classes ) + // array(url, name, extra css classes) // not part of $aside to make the template more adjustable $aside_sub = array( - 'site' => array($a->get_baseurl(true)."/admin/site/", t("Site") , "site"), - 'users' => array($a->get_baseurl(true)."/admin/users/", t("Users") , "users"), - 'plugins'=> array($a->get_baseurl(true)."/admin/plugins/", t("Plugins") , "plugins"), - 'themes' => array($a->get_baseurl(true)."/admin/themes/", t("Themes") , "themes"), - 'features' => array($a->get_baseurl(true)."/admin/features/", t("Additional features") , "features"), - 'dbsync' => array($a->get_baseurl(true)."/admin/dbsync/", t('DB updates'), "dbsync"), - 'queue' => array($a->get_baseurl(true)."/admin/queue/", t('Inspect Queue'), "queue"), - 'federation' => array($a->get_baseurl(true)."/admin/federation/", t('Federation Statistics'), "federation"), + 'site' => array("admin/site/", t("Site") , "site"), + 'users' => array("admin/users/", t("Users") , "users"), + 'plugins'=> array("admin/plugins/", t("Plugins") , "plugins"), + 'themes' => array("admin/themes/", t("Themes") , "themes"), + 'features' => array("admin/features/", t("Additional features") , "features"), + 'dbsync' => array("admin/dbsync/", t('DB updates'), "dbsync"), + 'queue' => array("admin/queue/", t('Inspect Queue'), "queue"), + 'federation' => array("admin/federation/", t('Federation Statistics'), "federation"), ); /* get plugins admin page */ @@ -169,18 +169,18 @@ function admin_content(&$a) { $aside_tools['plugins_admin']=array(); foreach ($r as $h){ $plugin =$h['name']; - $aside_tools['plugins_admin'][] = array($a->get_baseurl(true)."/admin/plugins/".$plugin, $plugin, "plugin"); + $aside_tools['plugins_admin'][] = array("admin/plugins/".$plugin, $plugin, "plugin"); // temp plugins with admin $a->plugins_admin[] = $plugin; } - $aside_tools['logs'] = array($a->get_baseurl(true)."/admin/logs/", t("Logs"), "logs"); - $aside_tools['viewlogs'] = array($a->get_baseurl(true)."/admin/viewlogs/", t("View Logs"), 'viewlogs'); - $aside_tools['diagnostics_probe'] = array($a->get_baseurl(true).'/probe/', t('probe address'), 'probe'); - $aside_tools['diagnostics_webfinger'] = array($a->get_baseurl(true).'/webfinger/', t('check webfinger'), 'webfinger'); + $aside_tools['logs'] = array("admin/logs/", t("Logs"), "logs"); + $aside_tools['viewlogs'] = array("admin/viewlogs/", t("View Logs"), 'viewlogs'); + $aside_tools['diagnostics_probe'] = array('probe/', t('probe address'), 'probe'); + $aside_tools['diagnostics_webfinger'] = array('webfinger/', t('check webfinger'), 'webfinger'); $t = get_markup_template("admin_aside.tpl"); - $a->page['aside'] .= replace_macros( $t, array( + $a->page['aside'] .= replace_macros($t, array( '$admin' => $aside_tools, '$subpages' => $aside_sub, '$admtxt' => t('Admin'), @@ -188,7 +188,7 @@ function admin_content(&$a) { '$logtxt' => t('Logs'), '$diagnosticstxt' => t('diagnostics'), '$h_pending' => t('User registrations waiting for confirmation'), - '$admurl'=> $a->get_baseurl(true)."/admin/" + '$admurl'=> "admin/" )); @@ -231,7 +231,7 @@ function admin_content(&$a) { $o = admin_page_federation($a); break; default: - notice( t("Item not found.") ); + notice(t("Item not found.")); } } else { $o = admin_page_summary($a); @@ -409,18 +409,18 @@ function admin_page_queue(&$a) { function admin_page_summary(&$a) { $r = q("SELECT `page-flags`, COUNT(uid) as `count` FROM `user` GROUP BY `page-flags`"); $accounts = array( - array( t('Normal Account'), 0), - array( t('Soapbox Account'), 0), - array( t('Community/Celebrity Account'), 0), - array( t('Automatic Friend Account'), 0), - array( t('Blog Account'), 0), - array( t('Private Forum'), 0) + array(t('Normal Account'), 0), + array(t('Soapbox Account'), 0), + array(t('Community/Celebrity Account'), 0), + array(t('Automatic Friend Account'), 0), + array(t('Blog Account'), 0), + array(t('Private Forum'), 0) ); $users=0; foreach ($r as $u){ $accounts[$u['page-flags']][1] = $u['count']; $users+= $u['count']; } - logger('accounts: ' . print_r($accounts,true),LOGGER_DATA); + logger('accounts: '.print_r($accounts,true),LOGGER_DATA); $r = q("SELECT COUNT(id) as `count` FROM `register`"); $pending = $r[0]['count']; @@ -433,7 +433,7 @@ function admin_page_summary(&$a) { // We can do better, but this is a quick queue status - $queues = array( 'label' => t('Message queues'), 'deliverq' => $deliverq, 'queue' => $queue ); + $queues = array('label' => t('Message queues'), 'deliverq' => $deliverq, 'queue' => $queue); $t = get_markup_template("admin_summary.tpl"); @@ -441,15 +441,15 @@ function admin_page_summary(&$a) { '$title' => t('Administration'), '$page' => t('Summary'), '$queues' => $queues, - '$users' => array( t('Registered users'), $users), + '$users' => array(t('Registered users'), $users), '$accounts' => $accounts, - '$pending' => array( t('Pending registrations'), $pending), - '$version' => array( t('Version'), FRIENDICA_VERSION), + '$pending' => array(t('Pending registrations'), $pending), + '$version' => array(t('Version'), FRIENDICA_VERSION), '$baseurl' => $a->get_baseurl(), '$platform' => FRIENDICA_PLATFORM, '$codename' => FRIENDICA_CODENAME, '$build' => get_config('system','build'), - '$plugins' => array( t('Active plugins'), $a->plugins ) + '$plugins' => array(t('Active plugins'), $a->plugins) )); } @@ -473,7 +473,7 @@ function admin_page_site_post(&$a) { $parsed = @parse_url($new_url); if(!$parsed || (!x($parsed,'host') || !x($parsed,'scheme'))) { notice(t("Can not parse base url. Must have at least <scheme>://<domain>")); - goaway($a->get_baseurl(true) . '/admin/site' ); + goaway('admin/site'); } /* steps: @@ -501,8 +501,8 @@ function admin_page_site_post(&$a) { $q = sprintf("UPDATE %s SET %s;", $table_name, $upds); $r = q($q); if(!$r) { - notice( "Failed updating '$table_name': " . $db->error ); - goaway($a->get_baseurl(true) . '/admin/site' ); + notice("Failed updating '$table_name': ".$db->error); + goaway('admin/site'); } } @@ -526,7 +526,7 @@ function admin_page_site_post(&$a) { info("Relocation started. Could take a while to complete."); - goaway($a->get_baseurl(true) . '/admin/site' ); + goaway('admin/site'); } // end relocate @@ -695,12 +695,12 @@ function admin_page_site_post(&$a) { set_config('system','language', $language); set_config('system','theme', $theme); - if( $theme_mobile === '---' ) { + if($theme_mobile === '---') { del_config('system','mobile-theme'); } else { set_config('system','mobile-theme', $theme_mobile); } - if( $singleuser === '---' ) { + if($singleuser === '---') { del_config('system','singleuser'); } else { set_config('system','singleuser', $singleuser); @@ -765,8 +765,8 @@ function admin_page_site_post(&$a) { set_config('system','embedly', $embedly); - info( t('Site settings updated.') . EOL); - goaway($a->get_baseurl(true) . '/admin/site' ); + info(t('Site settings updated.').EOL); + goaway('admin/site'); return; // NOTREACHED } @@ -797,12 +797,12 @@ function admin_page_site(&$a) { $files = glob('view/theme/*'); if($files) { foreach($files as $file) { - if(intval(file_exists($file . '/unsupported'))) + if(intval(file_exists($file.'/unsupported'))) continue; $f = basename($file); - $theme_name = ((file_exists($file . '/experimental')) ? sprintf("%s - \x28Experimental\x29", $f) : $f); - if(file_exists($file . '/mobile')) { + $theme_name = ((file_exists($file.'/experimental')) ? sprintf("%s - \x28Experimental\x29", $f) : $f); + if(file_exists($file.'/mobile')) { $theme_choices_mobile[$f] = $theme_name; } else { $theme_choices[$f] = $theme_name; @@ -1003,12 +1003,12 @@ function admin_page_dbsync(&$a) { $o = ''; if($a->argc > 3 && intval($a->argv[3]) && $a->argv[2] === 'mark') { - set_config('database', 'update_' . intval($a->argv[3]), 'success'); + set_config('database', 'update_'.intval($a->argv[3]), 'success'); $curr = get_config('system','build'); if(intval($curr) == intval($a->argv[3])) set_config('system','build',intval($curr) + 1); - info( t('Update has been marked successful') . EOL); - goaway($a->get_baseurl(true) . '/admin/dbsync'); + info(t('Update has been marked successful').EOL); + goaway('admin/dbsync'); } if(($a->argc > 2) AND (intval($a->argv[2]) OR ($a->argv[2] === 'check'))) { @@ -1026,7 +1026,7 @@ function admin_page_dbsync(&$a) { if($a->argc > 2 && intval($a->argv[2])) { require_once('update.php'); - $func = 'update_' . intval($a->argv[2]); + $func = 'update_'.intval($a->argv[2]); if(function_exists($func)) { $retval = $func(); if($retval === UPDATE_FAILED) { @@ -1082,18 +1082,18 @@ function admin_page_dbsync(&$a) { * @param App $a */ function admin_page_users_post(&$a){ - $pending = ( x($_POST, 'pending') ? $_POST['pending'] : array() ); - $users = ( x($_POST, 'user') ? $_POST['user'] : array() ); - $nu_name = ( x($_POST, 'new_user_name') ? $_POST['new_user_name'] : ''); - $nu_nickname = ( x($_POST, 'new_user_nickname') ? $_POST['new_user_nickname'] : ''); - $nu_email = ( x($_POST, 'new_user_email') ? $_POST['new_user_email'] : ''); + $pending = (x($_POST, 'pending') ? $_POST['pending'] : array()); + $users = (x($_POST, 'user') ? $_POST['user'] : array()); + $nu_name = (x($_POST, 'new_user_name') ? $_POST['new_user_name'] : ''); + $nu_nickname = (x($_POST, 'new_user_nickname') ? $_POST['new_user_nickname'] : ''); + $nu_email = (x($_POST, 'new_user_email') ? $_POST['new_user_email'] : ''); check_form_security_token_redirectOnErr('/admin/users', 'admin_users'); if(!($nu_name==="") && !($nu_email==="") && !($nu_nickname==="")) { require_once('include/user.php'); - $result = create_user( array('username'=>$nu_name, 'email'=>$nu_email, 'nickname'=>$nu_nickname, 'verified'=>1) ); + $result = create_user(array('username'=>$nu_name, 'email'=>$nu_email, 'nickname'=>$nu_nickname, 'verified'=>1)); if(! $result['success']) { notice($result['message']); return; @@ -1134,7 +1134,7 @@ function admin_page_users_post(&$a){ notification(array( 'type' => "SYSTEM_EMAIL", 'to_email' => $nu['email'], - 'subject'=> sprintf( t('Registration details for %s'), $a->config['sitename']), + 'subject'=> sprintf(t('Registration details for %s'), $a->config['sitename']), 'preamble'=> $preamble, 'body' => $body)); @@ -1143,17 +1143,17 @@ function admin_page_users_post(&$a){ if(x($_POST,'page_users_block')) { foreach($users as $uid){ q("UPDATE `user` SET `blocked`=1-`blocked` WHERE `uid`=%s", - intval( $uid ) + intval($uid) ); } - notice( sprintf( tt("%s user blocked/unblocked", "%s users blocked/unblocked", count($users)), count($users)) ); + notice(sprintf(tt("%s user blocked/unblocked", "%s users blocked/unblocked", count($users)), count($users))); } if(x($_POST,'page_users_delete')) { require_once("include/Contact.php"); foreach($users as $uid){ user_remove($uid); } - notice( sprintf( tt("%s user deleted", "%s users deleted", count($users)), count($users)) ); + notice(sprintf(tt("%s user deleted", "%s users deleted", count($users)), count($users))); } if(x($_POST,'page_users_approve')) { @@ -1168,7 +1168,7 @@ function admin_page_users_post(&$a){ user_deny($hash); } } - goaway($a->get_baseurl(true) . '/admin/users' ); + goaway('admin/users'); return; // NOTREACHED } @@ -1189,8 +1189,8 @@ function admin_page_users(&$a){ $uid = $a->argv[3]; $user = q("SELECT username, blocked FROM `user` WHERE `uid`=%d", intval($uid)); if(count($user)==0) { - notice( 'User not found' . EOL); - goaway($a->get_baseurl(true) . '/admin/users' ); + notice('User not found'.EOL); + goaway('admin/users'); return ''; // NOTREACHED } switch($a->argv[2]){ @@ -1200,18 +1200,18 @@ function admin_page_users(&$a){ require_once("include/Contact.php"); user_remove($uid); - notice( sprintf(t("User '%s' deleted"), $user[0]['username']) . EOL); + notice(sprintf(t("User '%s' deleted"), $user[0]['username']).EOL); }; break; case "block":{ check_form_security_token_redirectOnErr('/admin/users', 'admin_users', 't'); q("UPDATE `user` SET `blocked`=%d WHERE `uid`=%s", - intval( 1-$user[0]['blocked'] ), - intval( $uid ) + intval(1-$user[0]['blocked']), + intval($uid) ); - notice( sprintf( ($user[0]['blocked']?t("User '%s' unblocked"):t("User '%s' blocked")) , $user[0]['username']) . EOL); + notice(sprintf(($user[0]['blocked']?t("User '%s' unblocked"):t("User '%s' blocked")) , $user[0]['username']).EOL); }; break; } - goaway($a->get_baseurl(true) . '/admin/users' ); + goaway('admin/users'); return ''; // NOTREACHED } @@ -1230,7 +1230,7 @@ function admin_page_users(&$a){ $a->set_pager_itemspage(100); } - $users = q("SELECT `user` . * , `contact`.`name` , `contact`.`url` , `contact`.`micro`, `lastitem`.`lastitem_date`, `user`.`account_expired` + $users = q("SELECT `user`.* , `contact`.`name` , `contact`.`url` , `contact`.`micro`, `lastitem`.`lastitem_date`, `user`.`account_expired` FROM (SELECT MAX(`item`.`changed`) as `lastitem_date`, `item`.`uid` FROM `item` @@ -1277,7 +1277,7 @@ function admin_page_users(&$a){ while(count($users)) { $new_user = array(); - foreach( array_pop($users) as $k => $v) { + foreach(array_pop($users) as $k => $v) { $k = str_replace('-','_',$k); $new_user[$k] = $v; } @@ -1303,7 +1303,7 @@ function admin_page_users(&$a){ '$select_all' => t('select all'), '$h_pending' => t('User registrations waiting for confirm'), '$h_deleted' => t('User waiting for permanent deletion'), - '$th_pending' => array( t('Request date'), t('Name'), t('Email') ), + '$th_pending' => array(t('Request date'), t('Name'), t('Email')), '$no_pending' => t('No registrations.'), '$approve' => t('Approve'), '$deny' => t('Deny'), @@ -1315,8 +1315,8 @@ function admin_page_users(&$a){ '$h_users' => t('Users'), '$h_newuser' => t('New User'), - '$th_deleted' => array( t('Name'), t('Email'), t('Register date'), t('Last login'), t('Last item'), t('Deleted since') ), - '$th_users' => array( t('Name'), t('Email'), t('Register date'), t('Last login'), t('Last item'), t('Account') ), + '$th_deleted' => array(t('Name'), t('Email'), t('Register date'), t('Last login'), t('Last item'), t('Deleted since')), + '$th_users' => array(t('Name'), t('Email'), t('Register date'), t('Last login'), t('Last item'), t('Account')), '$confirm_delete_multi' => t('Selected users will be deleted!\n\nEverything these users had posted on this site will be permanently deleted!\n\nAre you sure?'), '$confirm_delete' => t('The user {0} will be deleted!\n\nEverything this user has posted on this site will be permanently deleted!\n\nAre you sure?'), @@ -1362,7 +1362,7 @@ function admin_page_plugins(&$a){ if($a->argc == 3) { $plugin = $a->argv[2]; if(!is_file("addon/$plugin/$plugin.php")) { - notice( t("Item not found.") ); + notice(t("Item not found.")); return ''; } @@ -1374,14 +1374,14 @@ function admin_page_plugins(&$a){ if($idx !== false) { unset($a->plugins[$idx]); uninstall_plugin($plugin); - info( sprintf( t("Plugin %s disabled."), $plugin ) ); + info(sprintf(t("Plugin %s disabled."), $plugin)); } else { $a->plugins[] = $plugin; install_plugin($plugin); - info( sprintf( t("Plugin %s enabled."), $plugin ) ); + info(sprintf(t("Plugin %s enabled."), $plugin)); } set_config("system","addon", implode(", ",$a->plugins)); - goaway($a->get_baseurl(true) . '/admin/plugins' ); + goaway('admin/plugins'); return ''; // NOTREACHED } @@ -1480,7 +1480,7 @@ function admin_page_plugins(&$a){ '$function' => 'plugins', '$plugins' => $plugins, '$pcount' => count($plugins), - '$noplugshint' => sprintf( t('There are currently no plugins available on your node. You can find the official plugin repository at %1$s and might find other interesting plugins in the open plugin registry at %2$s'), 'https://github.com/friendica/friendica-addons', 'http://addons.friendi.ca'), + '$noplugshint' => sprintf(t('There are currently no plugins available on your node. You can find the official plugin repository at %1$s and might find other interesting plugins in the open plugin registry at %2$s'), 'https://github.com/friendica/friendica-addons', 'http://addons.friendi.ca'), '$form_security_token' => get_form_security_token("admin_themes"), )); } @@ -1575,8 +1575,8 @@ function admin_page_themes(&$a){ if($files) { foreach($files as $file) { $f = basename($file); - $is_experimental = intval(file_exists($file . '/experimental')); - $is_supported = 1-(intval(file_exists($file . '/unsupported'))); + $is_experimental = intval(file_exists($file.'/experimental')); + $is_supported = 1-(intval(file_exists($file.'/unsupported'))); $is_allowed = intval(in_array($f,$allowed_themes)); if($is_allowed OR $is_supported OR get_config("system", "show_unsupported_themes")) @@ -1585,7 +1585,7 @@ function admin_page_themes(&$a){ } if(! count($themes)) { - notice( t('No themes found.')); + notice(t('No themes found.')); return ''; } @@ -1596,7 +1596,7 @@ function admin_page_themes(&$a){ if($a->argc == 3) { $theme = $a->argv[2]; if(! is_dir("view/theme/$theme")) { - notice( t("Item not found.") ); + notice(t("Item not found.")); return ''; } @@ -1609,15 +1609,15 @@ function admin_page_themes(&$a){ $s = rebuild_theme_table($themes); if($result) { install_theme($theme); - info( sprintf('Theme %s enabled.',$theme)); + info(sprintf('Theme %s enabled.',$theme)); } else { uninstall_theme($theme); - info( sprintf('Theme %s disabled.',$theme)); + info(sprintf('Theme %s disabled.',$theme)); } set_config('system','allowed_themes',$s); - goaway($a->get_baseurl(true) . '/admin/themes' ); + goaway('admin/themes'); return ''; // NOTREACHED } @@ -1663,7 +1663,7 @@ function admin_page_themes(&$a){ $admin_form = __get_theme_admin_form($a, $theme); } - $screenshot = array( get_theme_screenshot($theme), t('Screenshot')); + $screenshot = array(get_theme_screenshot($theme), t('Screenshot')); if(! stristr($screenshot[0],$theme)) $screenshot = null; @@ -1754,8 +1754,8 @@ function admin_page_logs_post(&$a) { set_config('system','loglevel', $loglevel); } - info( t("Log settings updated.") ); - goaway($a->get_baseurl(true) . '/admin/logs' ); + info(t("Log settings updated.")); + goaway('admin/logs'); return; // NOTREACHED } @@ -1803,7 +1803,7 @@ function admin_page_logs(&$a){ '$form_security_token' => get_form_security_token("admin_logs"), '$phpheader' => t("PHP logging"), '$phphint' => t("To enable logging of PHP errors and warnings you can add the following to the .htconfig.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."), - '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE );\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');", + '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE);\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');", )); } @@ -1871,7 +1871,7 @@ function admin_page_features_post(&$a) { check_form_security_token_redirectOnErr('/admin/features', 'admin_manage_features'); - logger('postvars: ' . print_r($_POST,true),LOGGER_DATA); + logger('postvars: '.print_r($_POST,true),LOGGER_DATA); $arr = array(); $features = get_features(false); @@ -1879,11 +1879,11 @@ function admin_page_features_post(&$a) { foreach($features as $fname => $fdata) { foreach(array_slice($fdata,1) as $f) { $feature = $f[0]; - $feature_state = 'feature_' . $feature; - $featurelock = 'featurelock_' . $feature; + $feature_state = 'feature_'.$feature; + $featurelock = 'featurelock_'.$feature; if(x($_POST[$feature_state])) - $val = intval($_POST['feature_' . $feature]); + $val = intval($_POST['feature_'.$feature]); else $val = 0; set_config('feature',$feature,$val); @@ -1895,7 +1895,7 @@ function admin_page_features_post(&$a) { } } - goaway($a->get_baseurl(true) . '/admin/features' ); + goaway('admin/features'); return; // NOTREACHED } @@ -1929,7 +1929,7 @@ function admin_page_features(&$a) { $set = $f[3]; $arr[$fname][1][] = array( array('feature_' .$f[0],$f[1],$set,$f[2],array(t('Off'),t('On'))), - array('featurelock_' .$f[0],sprintf( t('Lock feature %s'),$f[1]),(($f[4] !== false) ? "1" : ''),'',array(t('Off'),t('On'))) + array('featurelock_' .$f[0],sprintf(t('Lock feature %s'),$f[1]),(($f[4] !== false) ? "1" : ''),'',array(t('Off'),t('On'))) ); } } diff --git a/mod/common.php b/mod/common.php index c9409b3ef1..62a5185fed 100644 --- a/mod/common.php +++ b/mod/common.php @@ -40,7 +40,7 @@ function common_content(&$a) { $vcard_widget .= replace_macros(get_markup_template("vcard-widget.tpl"),array( '$name' => htmlentities($c[0]['name']), '$photo' => $c[0]['photo'], - 'url' => z_root() . '/contacts/' . $cid + 'url' => 'contacts/' . $cid )); if(! x($a->page,'aside')) diff --git a/mod/contacts.php b/mod/contacts.php index 0b421433e0..7f758b43c5 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -44,7 +44,7 @@ function contacts_init(&$a) { $vcard_widget = replace_macros(get_markup_template("vcard-widget.tpl"),array( '$name' => htmlentities($a->data['contact']['name']), '$photo' => $a->data['contact']['photo'], - '$url' => ($a->data['contact']['network'] == NETWORK_DFRN) ? z_root()."/redir/".$a->data['contact']['id'] : $a->data['contact']['url'], + '$url' => ($a->data['contact']['network'] == NETWORK_DFRN) ? "redir/".$a->data['contact']['id'] : $a->data['contact']['url'], '$addr' => (($a->data['contact']['addr'] != "") ? ($a->data['contact']['addr']) : ""), '$network_name' => $networkname, '$network' => t('Network:'), @@ -129,9 +129,9 @@ function contacts_batch_actions(&$a){ } if(x($_SESSION,'return_url')) - goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); + goaway('' . $_SESSION['return_url']); else - goaway($a->get_baseurl(true) . '/contacts'); + goaway('contacts'); } @@ -157,7 +157,7 @@ function contacts_post(&$a) { if(! count($orig_record)) { notice( t('Could not access contact record.') . EOL); - goaway($a->get_baseurl(true) . '/contacts'); + goaway('contacts'); return; // NOTREACHED } @@ -366,19 +366,19 @@ function contacts_content(&$a) { if(! count($orig_record)) { notice( t('Could not access contact record.') . EOL); - goaway($a->get_baseurl(true) . '/contacts'); + goaway('contacts'); return; // NOTREACHED } if($cmd === 'update') { _contact_update($contact_id); - goaway($a->get_baseurl(true) . '/contacts/' . $contact_id); + goaway('contacts/' . $contact_id); // NOTREACHED } if($cmd === 'updateprofile') { _contact_update_profile($contact_id); - goaway($a->get_baseurl(true) . '/crepair/' . $contact_id); + goaway('crepair/' . $contact_id); // NOTREACHED } @@ -389,7 +389,7 @@ function contacts_content(&$a) { info((($blocked) ? t('Contact has been blocked') : t('Contact has been unblocked')).EOL); } - goaway($a->get_baseurl(true) . '/contacts/' . $contact_id); + goaway('contacts/' . $contact_id); return; // NOTREACHED } @@ -400,7 +400,7 @@ function contacts_content(&$a) { info((($readonly) ? t('Contact has been ignored') : t('Contact has been unignored')).EOL); } - goaway($a->get_baseurl(true) . '/contacts/' . $contact_id); + goaway('contacts/' . $contact_id); return; // NOTREACHED } @@ -412,7 +412,7 @@ function contacts_content(&$a) { info((($archived) ? t('Contact has been archived') : t('Contact has been unarchived')).EOL); } - goaway($a->get_baseurl(true) . '/contacts/' . $contact_id); + goaway('contacts/' . $contact_id); return; // NOTREACHED } @@ -447,17 +447,17 @@ function contacts_content(&$a) { // Now check how the user responded to the confirmation query if($_REQUEST['canceled']) { if(x($_SESSION,'return_url')) - goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); + goaway('' . $_SESSION['return_url']); else - goaway($a->get_baseurl(true) . '/contacts'); + goaway('contacts'); } _contact_drop($contact_id, $orig_record[0]); info( t('Contact has been removed.') . EOL ); if(x($_SESSION,'return_url')) - goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); + goaway('' . $_SESSION['return_url']); else - goaway($a->get_baseurl(true) . '/contacts'); + goaway('contacts'); return; // NOTREACHED } if($cmd === 'posts') { @@ -575,7 +575,7 @@ function contacts_content(&$a) { '$lbl_info1' => t('Contact Information / Notes'), '$infedit' => t('Edit contact notes'), '$common_text' => $common_text, - '$common_link' => $a->get_baseurl(true) . '/common/loc/' . local_user() . '/' . $contact['id'], + '$common_link' => 'common/loc/' . local_user() . '/' . $contact['id'], '$all_friends' => $all_friends, '$relation_text' => $relation_text, '$visit' => sprintf( t('Visit %s\'s profile [%s]'),$contact['name'],$contact['url']), @@ -668,7 +668,7 @@ function contacts_content(&$a) { $tabs = array( array( 'label' => t('Suggestions'), - 'url' => $a->get_baseurl(true) . '/suggest', + 'url' => 'suggest', 'sel' => '', 'title' => t('Suggest potential friends'), 'id' => 'suggestions-tab', @@ -676,7 +676,7 @@ function contacts_content(&$a) { ), array( 'label' => t('All Contacts'), - 'url' => $a->get_baseurl(true) . '/contacts/all', + 'url' => 'contacts/all', 'sel' => ($all) ? 'active' : '', 'title' => t('Show all contacts'), 'id' => 'showall-tab', @@ -684,7 +684,7 @@ function contacts_content(&$a) { ), array( 'label' => t('Unblocked'), - 'url' => $a->get_baseurl(true) . '/contacts', + 'url' => 'contacts', 'sel' => ((! $all) && (! $blocked) && (! $hidden) && (! $search) && (! $nets) && (! $ignored) && (! $archived)) ? 'active' : '', 'title' => t('Only show unblocked contacts'), 'id' => 'showunblocked-tab', @@ -693,7 +693,7 @@ function contacts_content(&$a) { array( 'label' => t('Blocked'), - 'url' => $a->get_baseurl(true) . '/contacts/blocked', + 'url' => 'contacts/blocked', 'sel' => ($blocked) ? 'active' : '', 'title' => t('Only show blocked contacts'), 'id' => 'showblocked-tab', @@ -702,7 +702,7 @@ function contacts_content(&$a) { array( 'label' => t('Ignored'), - 'url' => $a->get_baseurl(true) . '/contacts/ignored', + 'url' => 'contacts/ignored', 'sel' => ($ignored) ? 'active' : '', 'title' => t('Only show ignored contacts'), 'id' => 'showignored-tab', @@ -711,7 +711,7 @@ function contacts_content(&$a) { array( 'label' => t('Archived'), - 'url' => $a->get_baseurl(true) . '/contacts/archived', + 'url' => 'contacts/archived', 'sel' => ($archived) ? 'active' : '', 'title' => t('Only show archived contacts'), 'id' => 'showarchived-tab', @@ -720,7 +720,7 @@ function contacts_content(&$a) { array( 'label' => t('Hidden'), - 'url' => $a->get_baseurl(true) . '/contacts/hidden', + 'url' => 'contacts/hidden', 'sel' => ($hidden) ? 'active' : '', 'title' => t('Only show hidden contacts'), 'id' => 'showhidden-tab', @@ -840,7 +840,7 @@ function contacts_tab($a, $contact_id, $active_tab) { 'accesskey' => 'd'); $tabs[] = array('label' => t('Repair'), - 'url' => $a->get_baseurl(true) . '/crepair/' . $contact_id, + 'url' => 'crepair/' . $contact_id, 'sel' => (($active_tab == 5)?'active':''), 'title' => t('Advanced Contact Settings'), 'id' => 'repair-tab', @@ -848,21 +848,21 @@ function contacts_tab($a, $contact_id, $active_tab) { $tabs[] = array('label' => (($contact['blocked']) ? t('Unblock') : t('Block') ), - 'url' => $a->get_baseurl(true) . '/contacts/' . $contact_id . '/block', + 'url' => 'contacts/' . $contact_id . '/block', 'sel' => '', 'title' => t('Toggle Blocked status'), 'id' => 'toggle-block-tab', 'accesskey' => 'b'); $tabs[] = array('label' => (($contact['readonly']) ? t('Unignore') : t('Ignore') ), - 'url' => $a->get_baseurl(true) . '/contacts/' . $contact_id . '/ignore', + 'url' => 'contacts/' . $contact_id . '/ignore', 'sel' => '', 'title' => t('Toggle Ignored status'), 'id' => 'toggle-ignore-tab', 'accesskey' => 'i'); $tabs[] = array('label' => (($contact['archive']) ? t('Unarchive') : t('Archive') ), - 'url' => $a->get_baseurl(true) . '/contacts/' . $contact_id . '/archive', + 'url' => 'contacts/' . $contact_id . '/archive', 'sel' => '', 'title' => t('Toggle Archive status'), 'id' => 'toggle-archive-tab', diff --git a/mod/content.php b/mod/content.php index c2b1546bf1..49cff74d2d 100644 --- a/mod/content.php +++ b/mod/content.php @@ -615,7 +615,7 @@ function render_content(&$a, $items, $mode, $update, $preview = false) { $comment_lastcollapsed = true; } - $redirect_url = $a->get_baseurl($ssl_state) . '/redir/' . $item['cid'] ; + $redirect_url = 'redir/' . $item['cid'] ; $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) diff --git a/mod/directory.php b/mod/directory.php index be09dd37f6..625f6c95ac 100644 --- a/mod/directory.php +++ b/mod/directory.php @@ -104,7 +104,7 @@ function directory_content(&$a) { $itemurl = (($rr['addr'] != "") ? $rr['addr'] : $rr['profile_url']); - $profile_link = z_root() . '/profile/' . ((strlen($rr['nickname'])) ? $rr['nickname'] : $rr['profile_uid']); + $profile_link = 'profile/' . ((strlen($rr['nickname'])) ? $rr['nickname'] : $rr['profile_uid']); $pdesc = (($rr['pdesc']) ? $rr['pdesc'] . '<br />' : ''); diff --git a/mod/message.php b/mod/message.php index 1724ebc424..734bf34710 100644 --- a/mod/message.php +++ b/mod/message.php @@ -13,7 +13,7 @@ function message_init(&$a) { $new = array( 'label' => t('New Message'), - 'url' => $a->get_baseurl(true) . '/message/new', + 'url' => 'message/new', 'sel'=> ($a->argv[1] == 'new'), 'accesskey' => 'm', ); @@ -90,7 +90,7 @@ function message_post(&$a) { $a->argv[1] = 'new'; } else - goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); + goaway($_SESSION['return_url']); } @@ -182,7 +182,7 @@ function message_content(&$a) { return; } - $myprofile = $a->get_baseurl(true) . '/profile/' . $a->user['nickname']; + $myprofile = 'profile/' . $a->user['nickname']; $tpl = get_markup_template('mail_head.tpl'); $header = replace_macros($tpl, array( @@ -221,7 +221,7 @@ function message_content(&$a) { } // Now check how the user responded to the confirmation query if($_REQUEST['canceled']) { - goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); + goaway($_SESSION['return_url']); } $cmd = $a->argv[1]; @@ -234,7 +234,7 @@ function message_content(&$a) { info( t('Message deleted.') . EOL ); } //goaway($a->get_baseurl(true) . '/message' ); - goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); + goaway($_SESSION['return_url']); } else { $r = q("SELECT `parent-uri`,`convid` FROM `mail` WHERE `id` = %d AND `uid` = %d LIMIT 1", @@ -265,7 +265,7 @@ function message_content(&$a) { info( t('Conversation removed.') . EOL ); } //goaway($a->get_baseurl(true) . '/message' ); - goaway($a->get_baseurl(true) . '/' . $_SESSION['return_url']); + goaway($_SESSION['return_url']); } } @@ -448,7 +448,7 @@ function message_content(&$a) { $sparkle = ''; } else { - $from_url = $a->get_baseurl(true) . '/redir/' . $message['contact-id']; + $from_url = 'redir/' . $message['contact-id']; $sparkle = ' sparkle'; } @@ -549,7 +549,7 @@ function render_messages($msg, $t) { $tpl = get_markup_template($t); $rslt = ''; - $myprofile = $a->get_baseurl(true) . '/profile/' . $a->user['nickname']; + $myprofile = 'profile/' . $a->user['nickname']; foreach($msg as $rr) { @@ -577,7 +577,7 @@ function render_messages($msg, $t) { $rslt .= replace_macros($tpl, array( '$id' => $rr['id'], '$from_name' => $participants, - '$from_url' => (($rr['network'] === NETWORK_DFRN) ? $a->get_baseurl(true) . '/redir/' . $rr['contact-id'] : $rr['url']), + '$from_url' => (($rr['network'] === NETWORK_DFRN) ? 'redir/' . $rr['contact-id'] : $rr['url']), '$sparkle' => ' sparkle', '$from_photo' => (($rr['thumb']) ? $rr['thumb'] : $rr['from-photo']), '$subject' => $subject_e, diff --git a/mod/notifications.php b/mod/notifications.php index a267b7c958..f6c4e8f51f 100644 --- a/mod/notifications.php +++ b/mod/notifications.php @@ -49,12 +49,12 @@ function notifications_post(&$a) { intval(local_user()) ); } - goaway($a->get_baseurl(true) . '/notifications/intros'); + goaway('notifications/intros'); } if($_POST['submit'] == t('Ignore')) { $r = q("UPDATE `intro` SET `ignore` = 1 WHERE `id` = %d", intval($intro_id)); - goaway($a->get_baseurl(true) . '/notifications/intros'); + goaway('notifications/intros'); } } } @@ -79,37 +79,37 @@ function notifications_content(&$a) { $tabs = array( array( 'label' => t('System'), - 'url'=>$a->get_baseurl(true) . '/notifications/system', + 'url'=>'notifications/system', 'sel'=> (($a->argv[1] == 'system') ? 'active' : ''), 'accesskey' => 'y', ), array( 'label' => t('Network'), - 'url'=>$a->get_baseurl(true) . '/notifications/network', + 'url'=>'notifications/network', 'sel'=> (($a->argv[1] == 'network') ? 'active' : ''), 'accesskey' => 'w', ), array( 'label' => t('Personal'), - 'url'=>$a->get_baseurl(true) . '/notifications/personal', + 'url'=>'notifications/personal', 'sel'=> (($a->argv[1] == 'personal') ? 'active' : ''), 'accesskey' => 'r', ), array( 'label' => t('Home'), - 'url' => $a->get_baseurl(true) . '/notifications/home', + 'url' => 'notifications/home', 'sel'=> (($a->argv[1] == 'home') ? 'active' : ''), 'accesskey' => 'h', ), array( 'label' => t('Introductions'), - 'url' => $a->get_baseurl(true) . '/notifications/intros', + 'url' => 'notifications/intros', 'sel'=> (($a->argv[1] == 'intros') ? 'active' : ''), 'accesskey' => 'i', ), /*array( 'label' => t('Messages'), - 'url' => $a->get_baseurl(true) . '/message', + 'url' => 'message', 'sel'=> '', ),*/ /*while I can have notifications for messages, this tablist is not place for message page link */ ); diff --git a/mod/photos.php b/mod/photos.php index a9dade6a81..2257a96653 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -80,7 +80,7 @@ function photos_init(&$a) { $entry = array( 'text' => $album['album'], 'total' => $album['total'], - 'url' => z_root() . '/photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album['album']), + 'url' => 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album['album']), 'urlencode' => urlencode($album['album']), 'bin2hex' => bin2hex($album['album']) ); @@ -100,7 +100,7 @@ function photos_init(&$a) { '$recent' => t('Recent Photos'), '$albums' => $albums['albums'], '$baseurl' => z_root(), - '$upload' => array( t('Upload New Photos'), $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/upload'), + '$upload' => array( t('Upload New Photos'), 'photos/' . $a->data['user']['nickname'] . '/upload'), '$can_post' => $can_post )); } @@ -190,7 +190,7 @@ function photos_post(&$a) { $album = hex2bin($a->argv[3]); if($album === t('Profile Photos') || $album === 'Contact Photos' || $album === t('Contact Photos')) { - goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); + goaway($_SESSION['photo_return']); return; // NOTREACHED } @@ -200,13 +200,13 @@ function photos_post(&$a) { ); if(! count($r)) { notice( t('Album not found.') . EOL); - goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); + goaway($_SESSION['photo_return']); return; // NOTREACHED } // Check if the user has responded to a delete confirmation query if($_REQUEST['canceled']) { - goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); + goaway($_SESSION['photo_return']); } /* @@ -221,7 +221,7 @@ function photos_post(&$a) { intval($page_owner_uid) ); $newurl = str_replace(bin2hex($album),bin2hex($newalbum),$_SESSION['photo_return']); - goaway($a->get_baseurl() . '/' . $newurl); + goaway($newurl); return; // NOTREACHED } @@ -273,7 +273,7 @@ function photos_post(&$a) { } } else { - goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); + goaway($_SESSION['photo_return']); return; // NOTREACHED } @@ -309,14 +309,14 @@ function photos_post(&$a) { } } } - goaway($a->get_baseurl() . '/photos/' . $a->data['user']['nickname']); + goaway('photos/' . $a->data['user']['nickname']); return; // NOTREACHED } // Check if the user has responded to a delete confirmation query for a single photo if(($a->argc > 2) && $_REQUEST['canceled']) { - goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); + goaway($_SESSION['photo_return']); } if(($a->argc > 2) && (x($_POST,'delete')) && ($_POST['delete'] == t('Delete Photo'))) { @@ -379,7 +379,7 @@ function photos_post(&$a) { } } - goaway($a->get_baseurl() . '/photos/' . $a->data['user']['nickname']); + goaway('photos/' . $a->data['user']['nickname']); return; // NOTREACHED } @@ -718,12 +718,6 @@ function photos_post(&$a) { $item_id = item_store($arr); if($item_id) { - //q("UPDATE `item` SET `plink` = '%s' WHERE `uid` = %d AND `id` = %d", - // dbesc($a->get_baseurl() . '/display/' . $owner_record['nickname'] . '/' . $item_id), - // intval($page_owner_uid), - // intval($item_id) - //); - proc_run('php',"include/notifier.php","tag","$item_id"); } } @@ -731,7 +725,7 @@ function photos_post(&$a) { } } - goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); + goaway($_SESSION['photo_return']); return; // NOTREACHED } @@ -938,14 +932,6 @@ function photos_post(&$a) { $item_id = item_store($arr); - //if($item_id) { - // q("UPDATE `item` SET `plink` = '%s' WHERE `uid` = %d AND `id` = %d", - // dbesc($a->get_baseurl() . '/display/' . $owner_record['nickname'] . '/' . $item_id), - // intval($page_owner_uid), - // intval($item_id) - // ); - //} - if($visible) proc_run('php', "include/notifier.php", 'wall-new', $item_id); @@ -954,7 +940,7 @@ function photos_post(&$a) { // addon uploaders should call "killme()" [e.g. exit] within the photo_post_end hook // if they do not wish to be redirected - goaway($a->get_baseurl() . '/' . $_SESSION['photo_return']); + goaway($_SESSION['photo_return']); // NOTREACHED } @@ -1125,7 +1111,7 @@ function photos_content(&$a) { $uploader = ''; - $ret = array('post_url' => $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'], + $ret = array('post_url' => 'photos/' . $a->data['user']['nickname'], 'addon_text' => $uploader, 'default_upload' => true); @@ -1267,15 +1253,15 @@ function photos_content(&$a) { else { if(($album !== t('Profile Photos')) && ($album !== 'Contact Photos') && ($album !== t('Contact Photos'))) { if($can_post) { - $edit = array(t('Edit Album'), $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album) . '/edit'); + $edit = array(t('Edit Album'), 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album) . '/edit'); } } } if($_GET['order'] === 'posted') - $order = array(t('Show Newest First'), $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album)); + $order = array(t('Show Newest First'), 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album)); else - $order = array(t('Show Oldest First'), $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album) . '?f=&order=posted'); + $order = array(t('Show Oldest First'), 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($album) . '?f=&order=posted'); $photos = array(); @@ -1301,10 +1287,10 @@ function photos_content(&$a) { $photos[] = array( 'id' => $rr['id'], 'twist' => ' ' . $twist . rand(2,4), - 'link' => $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/image/' . $rr['resource-id'] + 'link' => 'photos/' . $a->data['user']['nickname'] . '/image/' . $rr['resource-id'] . (($_GET['order'] === 'posted') ? '?f=&order=posted' : ''), 'title' => t('View Photo'), - 'src' => $a->get_baseurl() . '/photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' .$ext, + 'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' .$ext, 'alt' => $imgalt_e, 'desc'=> $desc_e, 'ext' => $ext, @@ -1317,7 +1303,7 @@ function photos_content(&$a) { '$photos' => $photos, '$album' => $album, '$can_post' => $can_post, - '$upload' => array(t('Upload New Photos'), $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/upload/' . bin2hex($album)), + '$upload' => array(t('Upload New Photos'), 'photos/' . $a->data['user']['nickname'] . '/upload/' . bin2hex($album)), '$order' => $order, '$edit' => $edit )); @@ -1384,8 +1370,8 @@ function photos_content(&$a) { } } $edit_suffix = ((($cmd === 'edit') && ($can_post)) ? '/edit' : ''); - $prevlink = $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/image/' . $prvnxt[$prv]['resource-id'] . $edit_suffix . (($_GET['order'] === 'posted') ? '?f=&order=posted' : ''); - $nextlink = $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/image/' . $prvnxt[$nxt]['resource-id'] . $edit_suffix . (($_GET['order'] === 'posted') ? '?f=&order=posted' : ''); + $prevlink = 'photos/' . $a->data['user']['nickname'] . '/image/' . $prvnxt[$prv]['resource-id'] . $edit_suffix . (($_GET['order'] === 'posted') ? '?f=&order=posted' : ''); + $nextlink = 'photos/' . $a->data['user']['nickname'] . '/image/' . $prvnxt[$nxt]['resource-id'] . $edit_suffix . (($_GET['order'] === 'posted') ? '?f=&order=posted' : ''); } @@ -1402,14 +1388,14 @@ function photos_content(&$a) { } } - $album_link = $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($ph[0]['album']); + $album_link = 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($ph[0]['album']); $tools = Null; $lock = Null; if($can_post && ($ph[0]['uid'] == $owner_uid)) { $tools = array( - 'edit' => array($a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/image/' . $datum . (($cmd === 'edit') ? '' : '/edit'), (($cmd === 'edit') ? t('View photo') : t('Edit photo'))), - 'profile'=>array($a->get_baseurl() . '/profile_photo/use/'.$ph[0]['resource-id'], t('Use as profile photo')), + 'edit' => array('photos/' . $a->data['user']['nickname'] . '/image/' . $datum . (($cmd === 'edit') ? '' : '/edit'), (($cmd === 'edit') ? t('View photo') : t('Edit photo'))), + 'profile'=>array('profile_photo/use/'.$ph[0]['resource-id'], t('Use as profile photo')), ); // lock @@ -1433,9 +1419,9 @@ function photos_content(&$a) { $prevlink = array($prevlink, '<div class="icon prev"></div>') ; $photo = array( - 'href' => $a->get_baseurl() . '/photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']], + 'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']], 'title'=> t('View Full Size'), - 'src' => $a->get_baseurl() . '/photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?f=&_u=' . datetime_convert('','','','ymdhis'), + 'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?f=&_u=' . datetime_convert('','','','ymdhis'), 'height' => $hires['height'], 'width' => $hires['width'], 'album' => $hires['album'], @@ -1522,7 +1508,7 @@ function photos_content(&$a) { } $tags = array(t('Tags: '), $tag_str); if($cmd === 'edit') { - $tags[] = $a->get_baseurl() . '/tagrm/' . $link_item['id']; + $tags[] = 'tagrm/' . $link_item['id']; $tags[] = t('[Remove any tag]'); } } @@ -1693,7 +1679,7 @@ function photos_content(&$a) { if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) continue; - $redirect_url = $a->get_baseurl() . '/redir/' . $item['cid'] ; + $redirect_url = 'redir/' . $item['cid'] ; if(local_user() && ($item['contact-uid'] == local_user()) @@ -1880,12 +1866,12 @@ function photos_content(&$a) { $photos[] = array( 'id' => $rr['id'], 'twist' => ' ' . $twist . rand(2,4), - 'link' => $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/image/' . $rr['resource-id'], + 'link' => 'photos/' . $a->data['user']['nickname'] . '/image/' . $rr['resource-id'], 'title' => t('View Photo'), - 'src' => $a->get_baseurl() . '/photo/' . $rr['resource-id'] . '-' . ((($rr['scale']) == 6) ? 4 : $rr['scale']) . '.' . $ext, + 'src' => 'photo/' . $rr['resource-id'] . '-' . ((($rr['scale']) == 6) ? 4 : $rr['scale']) . '.' . $ext, 'alt' => $alt_e, 'album' => array( - 'link' => $a->get_baseurl() . '/photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($rr['album']), + 'link' => 'photos/' . $a->data['user']['nickname'] . '/album/' . bin2hex($rr['album']), 'name' => $name_e, 'alt' => t('View Album'), ), @@ -1898,7 +1884,7 @@ function photos_content(&$a) { $o .= replace_macros($tpl, array( '$title' => t('Recent Photos'), '$can_post' => $can_post, - '$upload' => array(t('Upload New Photos'), $a->get_baseurl().'/photos/'.$a->data['user']['nickname'].'/upload'), + '$upload' => array(t('Upload New Photos'), 'photos/'.$a->data['user']['nickname'].'/upload'), '$photos' => $photos, )); diff --git a/mod/search.php b/mod/search.php index 7c78339c70..1776a92552 100644 --- a/mod/search.php +++ b/mod/search.php @@ -147,7 +147,7 @@ function search_content(&$a) { } - $o .= search($search,'search-box','/search',((local_user()) ? true : false), false); + $o .= search($search,'search-box','search',((local_user()) ? true : false), false); if(strpos($search,'#') === 0) { $tag = true; diff --git a/mod/settings.php b/mod/settings.php index 3efdbf6bde..905a5ed08d 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -39,7 +39,7 @@ function settings_init(&$a) { $tabs = array( array( 'label' => t('Account'), - 'url' => $a->get_baseurl(true).'/settings', + 'url' => 'settings', 'selected' => (($a->argc == 1) && ($a->argv[0] === 'settings')?'active':''), 'accesskey' => 'o', ), @@ -48,7 +48,7 @@ function settings_init(&$a) { if(get_features()) { $tabs[] = array( 'label' => t('Additional features'), - 'url' => $a->get_baseurl(true).'/settings/features', + 'url' => 'settings/features', 'selected' => (($a->argc > 1) && ($a->argv[1] === 'features') ? 'active' : ''), 'accesskey' => 't', ); @@ -56,49 +56,49 @@ function settings_init(&$a) { $tabs[] = array( 'label' => t('Display'), - 'url' => $a->get_baseurl(true).'/settings/display', + 'url' => 'settings/display', 'selected' => (($a->argc > 1) && ($a->argv[1] === 'display')?'active':''), 'accesskey' => 'i', ); $tabs[] = array( 'label' => t('Social Networks'), - 'url' => $a->get_baseurl(true).'/settings/connectors', + 'url' => 'settings/connectors', 'selected' => (($a->argc > 1) && ($a->argv[1] === 'connectors')?'active':''), 'accesskey' => 'w', ); $tabs[] = array( 'label' => t('Plugins'), - 'url' => $a->get_baseurl(true).'/settings/addon', + 'url' => 'settings/addon', 'selected' => (($a->argc > 1) && ($a->argv[1] === 'addon')?'active':''), 'accesskey' => 'l', ); $tabs[] = array( 'label' => t('Delegations'), - 'url' => $a->get_baseurl(true).'/delegate', + 'url' => 'delegate', 'selected' => (($a->argc == 1) && ($a->argv[0] === 'delegate')?'active':''), 'accesskey' => 'd', ); $tabs[] = array( 'label' => t('Connected apps'), - 'url' => $a->get_baseurl(true) . '/settings/oauth', + 'url' => 'settings/oauth', 'selected' => (($a->argc > 1) && ($a->argv[1] === 'oauth')?'active':''), 'accesskey' => 'b', ); $tabs[] = array( 'label' => t('Export personal data'), - 'url' => $a->get_baseurl(true) . '/uexport', + 'url' => 'uexport', 'selected' => (($a->argc == 1) && ($a->argv[0] === 'uexport')?'active':''), 'accesskey' => 'e', ); $tabs[] = array( 'label' => t('Remove account'), - 'url' => $a->get_baseurl(true) . '/removeme', + 'url' => 'removeme', 'selected' => (($a->argc == 1) && ($a->argv[0] === 'removeme')?'active':''), 'accesskey' => 'r', ); @@ -342,7 +342,7 @@ function settings_post(&$a) { ); call_hooks('display_settings_post', $_POST); - goaway($a->get_baseurl(true) . '/settings/display' ); + goaway('settings/display' ); return; // NOTREACHED } @@ -351,7 +351,7 @@ function settings_post(&$a) { if (x($_POST,'resend_relocate')) { proc_run('php', 'include/notifier.php', 'relocate', local_user()); info(t("Relocate message has been send to your contacts")); - goaway($a->get_baseurl(true) . '/settings'); + goaway('settings'); } call_hooks('settings_post', $_POST); @@ -627,7 +627,7 @@ function settings_post(&$a) { } - goaway($a->get_baseurl(true) . '/settings' ); + goaway('settings' ); return; // NOTREACHED } @@ -1152,7 +1152,7 @@ function settings_content(&$a) { info( t('Profile is <strong>not published</strong>.') . EOL ); - //$subdir = ((strlen($a->get_path())) ? '<br />' . t('or') . ' ' . $a->get_baseurl(true) . '/profile/' . $nickname : ''); + //$subdir = ((strlen($a->get_path())) ? '<br />' . t('or') . ' ' . 'profile/' . $nickname : ''); $tpl_addr = get_markup_template("settings_nick_set.tpl"); diff --git a/mod/uexport.php b/mod/uexport.php index a44620a976..3114add7e4 100644 --- a/mod/uexport.php +++ b/mod/uexport.php @@ -6,54 +6,6 @@ function uexport_init(&$a){ require_once("mod/settings.php"); settings_init($a); - -/* - $tabs = array( - array( - 'label' => t('Account settings'), - 'url' => $a->get_baseurl(true).'/settings', - 'selected' => '', - ), - array( - 'label' => t('Display settings'), - 'url' => $a->get_baseurl(true).'/settings/display', - 'selected' =>'', - ), - - array( - 'label' => t('Connector settings'), - 'url' => $a->get_baseurl(true).'/settings/connectors', - 'selected' => '', - ), - array( - 'label' => t('Plugin settings'), - 'url' => $a->get_baseurl(true).'/settings/addon', - 'selected' => '', - ), - array( - 'label' => t('Connected apps'), - 'url' => $a->get_baseurl(true) . '/settings/oauth', - 'selected' => '', - ), - array( - 'label' => t('Export personal data'), - 'url' => $a->get_baseurl(true) . '/uexport', - 'selected' => 'active' - ), - array( - 'label' => t('Remove account'), - 'url' => $a->get_baseurl(true) . '/removeme', - 'selected' => '' - ) - ); - - $tabtpl = get_markup_template("generic_links_widget.tpl"); - $a->page['aside'] = replace_macros($tabtpl, array( - '$title' => t('Settings'), - '$class' => 'settings-widget', - '$items' => $tabs, - )); -*/ } function uexport_content(&$a){ @@ -74,8 +26,8 @@ function uexport_content(&$a){ * list of array( 'link url', 'link text', 'help text' ) */ $options = array( - array('/uexport/account',t('Export account'),t('Export your account info and contacts. Use this to make a backup of your account and/or to move it to another server.')), - array('/uexport/backup',t('Export all'),t('Export your accout info, contacts and all your items as json. Could be a very big file, and could take a lot of time. Use this to make a full backup of your account (photos are not exported)')), + array('uexport/account',t('Export account'),t('Export your account info and contacts. Use this to make a backup of your account and/or to move it to another server.')), + array('uexport/backup',t('Export all'),t('Export your accout info, contacts and all your items as json. Could be a very big file, and could take a lot of time. Use this to make a full backup of your account (photos are not exported)')), ); call_hooks('uexport_options', $options); @@ -153,9 +105,9 @@ function uexport_account($a){ 'version' => FRIENDICA_VERSION, 'schema' => DB_UPDATE_VERSION, 'baseurl' => $a->get_baseurl(), - 'user' => $user, - 'contact' => $contact, - 'profile' => $profile, + 'user' => $user, + 'contact' => $contact, + 'profile' => $profile, 'photo' => $photo, 'pconfig' => $pconfig, 'group' => $group, @@ -171,8 +123,8 @@ function uexport_account($a){ * echoes account data and items as separated json, one per line */ function uexport_all(&$a) { - - uexport_account($a); + + uexport_account($a); echo "\n"; $r = q("SELECT count(*) as `total` FROM `item` WHERE `uid` = %d ", diff --git a/object/Item.php b/object/Item.php index 9daf44648e..e9c96cf159 100644 --- a/object/Item.php +++ b/object/Item.php @@ -50,7 +50,7 @@ class Item extends BaseObject { $this->writable = ($this->get_data_value('writable') || $this->get_data_value('self')); $ssl_state = ((local_user()) ? true : false); - $this->redirect_url = $a->get_baseurl($ssl_state) . '/redir/' . $this->get_data_value('cid') ; + $this->redirect_url = 'redir/' . $this->get_data_value('cid') ; if(get_config('system','thread_allow') && $a->theme_thread_allow && !$this->is_toplevel()) $this->threaded = true; @@ -119,9 +119,9 @@ class Item extends BaseObject { $shareable = ((($conv->get_profile_owner() == local_user()) && ($item['private'] != 1)) ? true : false); if(local_user() && link_compare($a->contact['url'],$item['author-link'])) { if ($item["event-id"] != 0) - $edpost = array($a->get_baseurl($ssl_state)."/events/event/".$item['event-id'], t("Edit")); + $edpost = array("events/event/".$item['event-id'], t("Edit")); else - $edpost = array($a->get_baseurl($ssl_state)."/editpost/".$item['id'], t("Edit")); + $edpost = array("editpost/".$item['id'], t("Edit")); } else $edpost = false; if(($this->get_data_value('uid') == local_user()) || $this->is_visiting()) @@ -160,7 +160,7 @@ class Item extends BaseObject { call_hooks('render_location',$locate); $location = ((strlen($locate['html'])) ? $locate['html'] : render_location_dummy($locate)); - $searchpath = $a->get_baseurl()."/search?tag="; + $searchpath = "search?tag="; $tags=array(); $hashtags = array(); $mentions = array(); @@ -703,7 +703,7 @@ class Item extends BaseObject { '$parent' => $this->get_id(), '$qcomment' => $qcomment, '$profile_uid' => $conv->get_profile_owner(), - '$mylink' => $a->contact['url'], + '$mylink' => $a->remove_baseurl($a->contact['url']), '$mytitle' => t('This is you'), '$myphoto' => $a->remove_baseurl($a->contact['thumb']), '$comment' => t('Comment'), diff --git a/view/templates/uexport.tpl b/view/templates/uexport.tpl index 382c7fc6a3..b9e177af26 100644 --- a/view/templates/uexport.tpl +++ b/view/templates/uexport.tpl @@ -1,10 +1,10 @@ -<h3>{{$title}}</h3> - - -{{foreach $options as $o}} -<dl> - <dt><a href="{{$baseurl}}/{{$o.0}}">{{$o.1}}</a></dt> - <dd>{{$o.2}}</dd> -</dl> -{{/foreach}} \ No newline at end of file +<h3>{{$title}}</h3> + + +{{foreach $options as $o}} +<dl> + <dt><a href="{{$o.0}}">{{$o.1}}</a></dt> + <dd>{{$o.2}}</dd> +</dl> +{{/foreach}} diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index e35556f541..3e91d61246 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -1432,8 +1432,8 @@ section.minimal { } .children .wall-item-container .wall-item-item .wall-item-content img { /* max-width: 650px; */ - /* max-width: 580px; */ - max-width: 100%; + max-width: 520px; + /* max-width: 100%; */ } .wall-item-container .wall-item-links, .wall-item-container .wall-item-actions { display: table-cell; diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php index f33a9178ac..7f6ead079f 100644 --- a/view/theme/vier/theme.php +++ b/view/theme/vier/theme.php @@ -239,12 +239,12 @@ function vier_community_info() { $selected = (($cid == $contact['id']) ? ' forum-selected' : ''); $entry = array( - 'url' => z_root() . '/network?f=&cid=' . $contact['id'], - 'external_url' => z_root() . '/redir/' . $contact['id'], + 'url' => 'network?f=&cid=' . $contact['id'], + 'external_url' => 'redir/' . $contact['id'], 'name' => $contact['name'], 'cid' => $contact['id'], 'selected' => $selected, - 'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO), + 'micro' => App::remove_baseurl(proxy_url($contact['micro'], false, PROXY_SIZE_MICRO)), 'id' => ++$id, ); $entries[] = $entry; From 9619829b4ab693bdbe2e24071e3d39d840d855b0 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 18 Feb 2016 07:41:08 +0100 Subject: [PATCH 116/273] Avoid "array_merge" warning. --- include/socgraph.php | 73 +++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/include/socgraph.php b/include/socgraph.php index bd5b1817f0..186326f42d 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -438,44 +438,47 @@ function poco_last_updated($profile, $force = false) { $noscrape = json_decode($noscraperet["body"], true); - $contact = array("url" => $profile, - "network" => $server[0]["network"], - "generation" => $gcontacts[0]["generation"]); + if (is_array($noscrape)) { + $contact = array("url" => $profile, + "network" => $server[0]["network"], + "generation" => $gcontacts[0]["generation"]); - $contact["name"] = $noscrape["fn"]; - $contact["community"] = $noscrape["comm"]; + $contact["name"] = $noscrape["fn"]; + $contact["community"] = $noscrape["comm"]; - if (isset($noscrape["tags"])) { - $keywords = implode(" ", $noscrape["tags"]); - if ($keywords != "") - $contact["keywords"] = $keywords; + if (isset($noscrape["tags"])) { + $keywords = implode(" ", $noscrape["tags"]); + if ($keywords != "") + $contact["keywords"] = $keywords; + } + + $location = formatted_location($noscrape); + if ($location) + $contact["location"] = $location; + + $contact["notify"] = $noscrape["dfrn-notify"]; + + // Remove all fields that are not present in the gcontact table + unset($noscrape["fn"]); + unset($noscrape["key"]); + unset($noscrape["homepage"]); + unset($noscrape["comm"]); + unset($noscrape["tags"]); + unset($noscrape["locality"]); + unset($noscrape["region"]); + unset($noscrape["country-name"]); + unset($noscrape["contacts"]); + unset($noscrape["dfrn-request"]); + unset($noscrape["dfrn-confirm"]); + unset($noscrape["dfrn-notify"]); + unset($noscrape["dfrn-poll"]); + + $contact = array_merge($contact, $noscrape); + + update_gcontact($contact); + + return $noscrape["updated"]; } - - $location = formatted_location($noscrape); - if ($location) - $contact["location"] = $location; - - $contact["notify"] = $noscrape["dfrn-notify"]; - - // Remove all fields that are not present in the gcontact table - unset($noscrape["fn"]); - unset($noscrape["key"]); - unset($noscrape["homepage"]); - unset($noscrape["comm"]); - unset($noscrape["tags"]); - unset($noscrape["locality"]); - unset($noscrape["region"]); - unset($noscrape["country-name"]); - unset($noscrape["contacts"]); - unset($noscrape["dfrn-request"]); - unset($noscrape["dfrn-confirm"]); - unset($noscrape["dfrn-notify"]); - unset($noscrape["dfrn-poll"]); - - $contact = array_merge($contact, $noscrape); - update_gcontact($contact); - - return $noscrape["updated"]; } } } From 8a3de7b1868b93fe61c9dac4e0b3a2c0cb1021d3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 19 Feb 2016 07:30:28 +0100 Subject: [PATCH 117/273] Issue 2367: The data for the gserver table is now sanitized. --- include/socgraph.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/socgraph.php b/include/socgraph.php index 186326f42d..33d62dc5b9 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -722,7 +722,8 @@ function poco_check_server($server_url, $network = "", $force = false) { // Will also return data for Friendica and GNU Social - but it will be overwritten later // The "not implemented" is a special treatment for really, really old Friendica versions $serverret = z_fetch_url($server_url."/api/statusnet/version.json"); - if ($serverret["success"] AND ($serverret["body"] != '{"error":"not implemented"}') AND ($serverret["body"] != '') AND (strlen($serverret["body"]) < 250)) { + if ($serverret["success"] AND ($serverret["body"] != '{"error":"not implemented"}') AND + ($serverret["body"] != '') AND (strlen($serverret["body"]) < 30)) { $platform = "StatusNet"; $version = trim($serverret["body"], '"'); $network = NETWORK_OSTATUS; @@ -730,7 +731,8 @@ function poco_check_server($server_url, $network = "", $force = false) { // Test for GNU Social $serverret = z_fetch_url($server_url."/api/gnusocial/version.json"); - if ($serverret["success"] AND ($serverret["body"] != '{"error":"not implemented"}') AND ($serverret["body"] != '') AND (strlen($serverret["body"]) < 250)) { + if ($serverret["success"] AND ($serverret["body"] != '{"error":"not implemented"}') AND + ($serverret["body"] != '') AND (strlen($serverret["body"]) < 30)) { $platform = "GNU Social"; $version = trim($serverret["body"], '"'); $network = NETWORK_OSTATUS; @@ -857,6 +859,11 @@ function poco_check_server($server_url, $network = "", $force = false) { // Check again if the server exists $servers = q("SELECT `nurl` FROM `gserver` WHERE `nurl` = '%s'", dbesc(normalise_link($server_url))); + $version = strip_tags($version); + $site_name = strip_tags($site_name); + $info = strip_tags($info); + $platform = strip_tags($platform); + if ($servers) q("UPDATE `gserver` SET `url` = '%s', `version` = '%s', `site_name` = '%s', `info` = '%s', `register_policy` = %d, `poco` = '%s', `noscrape` = '%s', `network` = '%s', `platform` = '%s', `last_contact` = '%s', `last_failure` = '%s' WHERE `nurl` = '%s'", From 8ec833f8087b0bba2f167471c430543c271602dc Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 22 Feb 2016 23:20:59 +0100 Subject: [PATCH 118/273] New BBCode element "abstract" for network depending messages. --- include/bbcode.php | 42 ++++++++++++++++++++++++++++++++++++++++++ include/dfrn.php | 4 ++++ include/plaintext.php | 43 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/include/bbcode.php b/include/bbcode.php index 6a44e19ec4..eb009477cf 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -851,6 +851,9 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true, $simplehtml = fal $a = get_app(); + // Remove the abstract element. It is a non visible element. + $Text = remove_abstract($Text); + // Hide all [noparse] contained bbtags by spacefying them // POSSIBLE BUG --> Will the 'preg' functions crash if there's an embedded image? @@ -1300,4 +1303,43 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true, $simplehtml = fal return trim($Text); } + +/** + * @brief Removes the "abstract" element from the text + * + * @param string $text The text with BBCode + * @return string The same text - but without "abstract" element + */ +function remove_abstract($text) { + $text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", '', $text); + $text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", '', $text); + + return $text; +} + +/** + * @brief Returns the value of the "abstract" element + * + * @param string $text The text that maybe contains the element + * @param string $addon The addon for which the abstract is meant for + * @return string The abstract + */ +function fetch_abstract($text, $addon = "") { + $abstract = ""; + $abstracts = array(); + $addon = strtolower($addon); + + if (preg_match_all("/\[abstract=(.*?)\](.*?)\[\/abstract\]/ism",$text, $results, PREG_SET_ORDER)) + foreach ($results AS $result) + $abstracts[strtolower($result[1])] = $result[2]; + + if (isset($abstracts[$addon])) + $abstract = $abstracts[$addon]; + + if ($abstract == "") + if (preg_match("/\[abstract\](.*?)\[\/abstract\]/ism",$text, $result)) + $abstract = $result[1]; + + return $abstract; +} ?> diff --git a/include/dfrn.php b/include/dfrn.php index f7a05bdb63..ad04a91295 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -18,6 +18,7 @@ require_once("include/event.php"); require_once("include/text.php"); require_once("include/oembed.php"); require_once("include/html2bbcode.php"); +require_once("include/bbcode.php"); /** * @brief This class contain functions to create and send DFRN XML files @@ -720,6 +721,9 @@ class dfrn { else $body = $item['body']; + // Remove the abstract element. It is only locally important. + $body = remove_abstract($body); + if ($type == 'html') { $htmlbody = $body; diff --git a/include/plaintext.php b/include/plaintext.php index 05431bee2d..199abcbb31 100644 --- a/include/plaintext.php +++ b/include/plaintext.php @@ -132,7 +132,7 @@ function shortenmsg($msg, $limit, $twitter = false) { return($msg); } -function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { +function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2, $target_network = "") { require_once("include/bbcode.php"); require_once("include/html2plain.php"); require_once("include/network.php"); @@ -144,6 +144,9 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { // Add an URL element if the text contains a raw link $body = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url]$2[/url]', $body); + // Remove the abstract + $body = remove_abstract($body); + // At first look at data that is attached via "type-..." stuff // This will hopefully replaced with a dedicated bbcode later //$post = get_attached_data($b["body"]); @@ -154,6 +157,44 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { elseif ($b["title"] != "") $post["text"] = trim($b["title"]); + $abstract = ""; + + // Fetch the abstract from the given target network + if ($target_network != "") { + $default_abstract = fetch_abstract($b["body"]); + $abstract = fetch_abstract($b["body"], $target_network); + + // If we post to a network with no limit we only fetch + // an abstract exactly for this network + if (($limit == 0) AND ($abstract == $default_abstract)) + $abstract = ""; + + } else // Try to guess the correct target network + switch ($htmlmode) { + case 8: + $abstract = fetch_abstract($b["body"], NETWORK_TWITTER); + break; + case 7: + $abstract = fetch_abstract($b["body"], NETWORK_STATUSNET); + break; + case 6: + $abstract = fetch_abstract($b["body"], NETWORK_APPNET); + break; + default: // We don't know the exact target. + // We fetch an abstract since there is a posting limit. + if ($limit > 0) + $abstract = fetch_abstract($b["body"]); + } + + if ($abstract != "") { + $post["text"] = $abstract; + + if ($post["type"] == "text") { + $post["type"] = "link"; + $post["url"] = $b["plink"]; + } + } + $html = bbcode($post["text"], false, false, $htmlmode); $msg = html2plain($html, 0, true); $msg = trim(html_entity_decode($msg,ENT_QUOTES,'UTF-8')); From bc283a5316e7f58edd26a93d8811b30527d8100c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 23 Feb 2016 07:21:40 +0100 Subject: [PATCH 119/273] The "abstract" has moved after the "nobb" part in bbcode. --- include/bbcode.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/bbcode.php b/include/bbcode.php index eb009477cf..c1156e3afe 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -851,9 +851,6 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true, $simplehtml = fal $a = get_app(); - // Remove the abstract element. It is a non visible element. - $Text = remove_abstract($Text); - // Hide all [noparse] contained bbtags by spacefying them // POSSIBLE BUG --> Will the 'preg' functions crash if there's an embedded image? @@ -861,6 +858,8 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true, $simplehtml = fal $Text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'bb_spacefy',$Text); $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy',$Text); + // Remove the abstract element. It is a non visible element. + $Text = remove_abstract($Text); // Move all spaces out of the tags $Text = preg_replace("/\[(\w*)\](\s*)/ism", '$2[$1]', $Text); From 148c89a20d3fa75904f87d4b7fc0e70077ace85a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 23 Feb 2016 07:56:49 +0100 Subject: [PATCH 120/273] Added documentation --- include/plaintext.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/plaintext.php b/include/plaintext.php index 199abcbb31..a2b2c56522 100644 --- a/include/plaintext.php +++ b/include/plaintext.php @@ -132,6 +132,18 @@ function shortenmsg($msg, $limit, $twitter = false) { return($msg); } +/** + * @brief Convert a message into plaintext for connectors to other networks + * + * @param App $a The application class + * @param array $b The message array that is about to be posted + * @param int $limit The maximum number of characters when posting to that network + * @param bool $includedlinks Has an attached link to be included into the message? + * @param int $htmlmode This triggers the behaviour of the bbcode conversion + * @param string $target_network Name of the network where the post should go to. + * + * @return string The converted message + */ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2, $target_network = "") { require_once("include/bbcode.php"); require_once("include/html2plain.php"); From b93326bb23747bbe03c84119447f429009829e49 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Tue, 23 Feb 2016 19:26:03 +0100 Subject: [PATCH 121/273] contactedit-actions-button: use internal links instead of full links --- mod/contacts.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mod/contacts.php b/mod/contacts.php index 991b59885c..4897663a05 100644 --- a/mod/contacts.php +++ b/mod/contacts.php @@ -970,7 +970,7 @@ function contact_actions($contact) { if($contact['network'] === NETWORK_DFRN) { $contact_actions['suggest'] = array( 'label' => t('Suggest friends'), - 'url' => app::get_baseurl(true) . '/fsuggest/' . $contact['id'], + 'url' => 'fsuggest/' . $contact['id'], 'title' => '', 'sel' => '', 'id' => 'suggest', @@ -980,7 +980,7 @@ function contact_actions($contact) { if($poll_enabled) { $contact_actions['update'] = array( 'label' => t('Update now'), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/update', + 'url' => 'contacts/' . $contact['id'] . '/update', 'title' => '', 'sel' => '', 'id' => 'update', @@ -989,7 +989,7 @@ function contact_actions($contact) { $contact_actions['block'] = array( 'label' => (intval($contact['blocked']) ? t('Unblock') : t('Block') ), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/block', + 'url' => 'contacts/' . $contact['id'] . '/block', 'title' => t('Toggle Blocked status'), 'sel' => (intval($contact['blocked']) ? 'active' : ''), 'id' => 'toggle-block', @@ -997,7 +997,7 @@ function contact_actions($contact) { $contact_actions['ignore'] = array( 'label' => (intval($contact['readonly']) ? t('Unignore') : t('Ignore') ), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/ignore', + 'url' => 'contacts/' . $contact['id'] . '/ignore', 'title' => t('Toggle Ignored status'), 'sel' => (intval($contact['readonly']) ? 'active' : ''), 'id' => 'toggle-ignore', @@ -1005,7 +1005,7 @@ function contact_actions($contact) { $contact_actions['archive'] = array( 'label' => (intval($contact['archive']) ? t('Unarchive') : t('Archive') ), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/archive', + 'url' => 'contacts/' . $contact['id'] . '/archive', 'title' => t('Toggle Archive status'), 'sel' => (intval($contact['archive']) ? 'active' : ''), 'id' => 'toggle-archive', @@ -1013,7 +1013,7 @@ function contact_actions($contact) { $contact_actions['delete'] = array( 'label' => t('Delete'), - 'url' => app::get_baseurl(true) . '/contacts/' . $contact['id'] . '/drop', + 'url' => 'contacts/' . $contact['id'] . '/drop', 'title' => t('Delete contact'), 'sel' => '', 'id' => 'delete', From 86339cdb976f4ca28499b61e849cbcaa3f245e8b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 23 Feb 2016 22:45:06 +0100 Subject: [PATCH 122/273] Some documentation for the usage of the "abstract" element. --- doc/BBCode.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++-- doc/de/BBCode.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/doc/BBCode.md b/doc/BBCode.md index fe7c1481f6..a45431f586 100644 --- a/doc/BBCode.md +++ b/doc/BBCode.md @@ -143,6 +143,62 @@ Map You can embed maps from coordinates or addresses. This require "openstreetmap" addon version 1.3 or newer. +----------------------------------------------------------- + +Abstract for longer posts +------------------------- + +If you want to spread your post to several third party networks you can +have the problem that these networks have (for example) a length +limitation. (Like on Twitter) + +Friendica is using a semi intelligent mechanism to generate a fitting +abstract. But it can be interesting to define an own abstract that will +only be displayed on the external network. This is done with the +[abstract]-element. Example: + +<pre>[abstract]Totally interesting! A must-see! Please click the link![/abstract] +I want to tell you a really boring story that you really never wanted +to hear.</pre> + +Twitter would display the text "Totally interesting! A must-see! Please +click the link!". On Friendica you would only see the text after "I +want to tell you a really ..." + +It is even possible to define abstracts for separate networks: + +<pre> +[abstract]Hi friends Here are my newest pictures![abstract] +[abstract=twit]Hi my dear Twitter followers. Do you want to see my new +pictures?[abstract] +[abstract=apdn]Helly my dear followers on ADN. I made sone new pictures +that I wanted to share with you.[abstract] +Today I was in the woods and took some real cool pictures ... +</pre> + +For Twitter and App.net the system will use the defined abstracts. For +other networks (e.g. when you are using the Statusnet connector) the +general abstract element will be used. + +If you use (for example) the "buffer" connector to post to Facebook or +Google+ you can use this element to define an abstract for a longer +blogpost that you don't want to post completely to these networks. + +Networks like Facebook or Google+ aren't length limited. For this reason +the [abstract] element isn't used. Instead you have to name the explicit +network: + +<pre> +[abstract]These days I had a strange encounter ...[abstract] +[abstract=goog]Helly my dear Google+ followers. You have to read my +newest blog post![abstract] +[abstract=face]Hello my Facebook friends. These days happened something +really cool.[abstract] +While taking pictures in the woods I had a really strange encounter ... </pre> + +The [abstract] element isn't working with the native OStatus connection +or with connectors where we post the HTML. (Like Tumblr, Wordpress or +Pump.io) Special ------- @@ -150,5 +206,3 @@ Special If you need to put literal bbcode in a message, [noparse], [nobb] or [pre] are used to escape bbcode: <pre>[noparse][b]bold[/b][/noparse]</pre> : [b]bold[/b] - - diff --git a/doc/de/BBCode.md b/doc/de/BBCode.md index d3e205f0fa..0856c30e50 100644 --- a/doc/de/BBCode.md +++ b/doc/de/BBCode.md @@ -145,6 +145,64 @@ eine Karte von [OpenStreetmap](http://openstreetmap.org) eingebettet werden. Zur oder eine Adresse in obiger Form verwendet werden. +Zusammenfassung für längere Beiträge +------------------------------------ + +Wenn man seine Beiträge über mehrere Netzwerke verbreiten möchte, hat +man häufig das Problem, dass diese Netzwerke z.B. eine +Längenbeschränkung haben. (Z.B. Twitter). + +Friendica benutzt zum Erzeugen eines Anreißtextes eine halbwegs +intelligente Logik. Es kann aber dennoch von Interesse sein, eine eigene +Zusammenfassung zu erstellen, die nur auf dem Fremdnetzwerk dargestellt +wird. Dies geschieht mit dem [abstract]-Element. Beispiel: + +<pre>[abstract]Total spannend! Unbedingt diesen Link anklicken![/abstract] +Hier erzähle ich euch eine total langweilige Geschichte, die ihr noch +nie hören wolltet.</pre> + +Auf Twitter würde das "Total spannend! Unbedingt diesen Link anklicken!" +stehen, auf Friendica würde nur der Text nach "Hier erzähle ..." +erscheinen. + +Es ist sogar möglich, für einzelne Netzwerke eigene Zusammenfassungen zu +erstellen: + +<pre> +[abstract]Hallo Leute, hier meine neuesten Bilder![abstract] +[abstract=twit]Hallo Twitter-User, hier meine neuesten Bilder![abstract] +[abstract=apdn]Hallo App.net-User, hier meine neuesten Bilder![abstract] +Ich war heute wieder im Wald unterwegs und habe tolle Bilder geschossen ... +</pre> + +Für Twitter und App.net nimmt das System die entsprechenden Texte. Bei +anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim +Statusnet-Connector) wird dann die Zusammenfassung unter [abstract] +verwendet. + +Wenn man z.B. den "buffer"-Connector verwendet, um nach Facebook oder +Google+ zu posten, kann man dieses Element ebenfalls verwenden, wenn man +z.B. einen längeren Blogbeitrag erstellt hat, aber ihn nicht komplett in +diese Netzwerke posten möchte. + +Netzwerke wie Facebook oder Google+ sind nicht in der Postinglänge +beschränkt. Aus diesem Grund greift nicht die +[abstract]-Zusammenfassung. Stattdessen muss man das Netzwerk explizit +angeben: + +<pre> +[abstract]Ich habe neulich wieder etwas erlebt, was ich euch mitteilen möchte.[abstract] +[abstract=goog]Hallo meine Google+-Kreislinge. Ich habe neulich wieder +etwas erlebt, was ich euch mitteilen möchte.[abstract] +[abstract=face]Hallo Facebook-Freunde! Ich habe neulich wieder etwas +erlebt, was ich euch mitteilen möchte.[abstract] +Beim Bildermachen im Wald habe ich neulich eine interessante Person +getroffen ... </pre> + +Das [abstract]-Element greift nicht bei der nativen OStatus-Verbindung +oder bei Connectoren, die den HTML-Text posten wie z.B. die Connectoren +zu Tumblr, Wordpress oder Pump.io. + Spezielle Tags ------- From a153f49920b2f563cd7d8f8eed36e94fa8d55a11 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Fri, 5 Feb 2016 06:47:17 +0100 Subject: [PATCH 123/273] clarification to the docs --- doc/Bugs-and-Issues.md | 2 ++ doc/Connectors.md | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/Bugs-and-Issues.md b/doc/Bugs-and-Issues.md index 366b2ed662..0ece265a24 100644 --- a/doc/Bugs-and-Issues.md +++ b/doc/Bugs-and-Issues.md @@ -6,6 +6,8 @@ Bugs and Issues If your server has a support page, you should report any bugs/issues you encounter there first. Reporting to your support page before reporting to the developers makes their job easier, as they don't have to deal with bug reports that might not have anything to do with them. This helps us get new features faster. +You can also contact the [friendica support forum](https://helpers.pyxis.uberspace.de/profile/helpers) and report your problem there. +Maybe someone from another node encountered the problem as well and can help you. If you're a technical user, or your site doesn't have a support page, you'll need to use the [Bug Tracker](http://bugs.friendica.com/). Please perform a search to see if there's already an open bug that matches yours before submitting anything. diff --git a/doc/Connectors.md b/doc/Connectors.md index cd4b643f14..148352c552 100644 --- a/doc/Connectors.md +++ b/doc/Connectors.md @@ -57,13 +57,15 @@ All that the pages need to have is a discoverable feed using either the RSS or A Twitter --- -To follow a Twitter member, put the URL of the Twitter member's main page into the Connect box on your [Contacts](contacts) page. +To follow a Twitter member, the Twitter-Connector (Addon) needs to be configured on your node. +If this is the case put the URL of the Twitter member's main page into the Connect box on your [Contacts](contacts) page. To reply, you must have the Twitter connector installed, and reply using your own status editor. Begin the message with @twitterperson replacing with the Twitter username. Email --- +If the php module for IMAP support is available on your server, Friendica can connect to email contacts as well. Configure the email connector from your [Settings](settings) page. Once this has been done, you may enter an email address to connect with using the Connect box on your [Contacts](contacts) page. They must be the sender of a message which is currently in your INBOX for the connection to succeed. From eec0dbf7387db818fbee4f4cd8c2e96af73c8418 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 24 Feb 2016 07:20:23 +0100 Subject: [PATCH 124/273] Bugfix: Resharing on the display page works again. --- mod/display.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mod/display.php b/mod/display.php index 4e33927072..e088b2fbbd 100644 --- a/mod/display.php +++ b/mod/display.php @@ -347,10 +347,8 @@ function display_content(&$a, $update = 0) { return; } - // Why do we need this on the display page? We don't have the possibility to write new content here. - // Ad editing of posts work without this as well. - // We should remove this completely for the 3.5.1 release. - /* + // We meed the editor here if we want to reshare an item. + if ($is_owner) { $x = array( 'is_owner' => true, @@ -366,7 +364,6 @@ function display_content(&$a, $update = 0) { ); $o .= status_editor($a,$x,0,true); } - */ $sql_extra = item_permissions_sql($a->profile['uid'],$remote_contact,$groups); From 99bac3a4d8cc75651b3efaf6fe55f2b7eb4fc95b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 24 Feb 2016 07:22:32 +0100 Subject: [PATCH 125/273] Corrected explanation --- mod/display.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/display.php b/mod/display.php index e088b2fbbd..97261e267d 100644 --- a/mod/display.php +++ b/mod/display.php @@ -347,7 +347,7 @@ function display_content(&$a, $update = 0) { return; } - // We meed the editor here if we want to reshare an item. + // We need the editor here to be able to reshare an item. if ($is_owner) { $x = array( From db9266346675b164a37c1ff3ea393f02d37af47f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 24 Feb 2016 07:33:20 +0100 Subject: [PATCH 126/273] One line, one sentence --- doc/BBCode.md | 29 +++++++---------------------- doc/de/BBCode.md | 40 +++++++++------------------------------- 2 files changed, 16 insertions(+), 53 deletions(-) diff --git a/doc/BBCode.md b/doc/BBCode.md index a45431f586..75cad5d96f 100644 --- a/doc/BBCode.md +++ b/doc/BBCode.md @@ -148,22 +148,15 @@ This require "openstreetmap" addon version 1.3 or newer. Abstract for longer posts ------------------------- -If you want to spread your post to several third party networks you can -have the problem that these networks have (for example) a length -limitation. (Like on Twitter) +If you want to spread your post to several third party networks you can have the problem that these networks have (for example) a length limitation. (Like on Twitter) -Friendica is using a semi intelligent mechanism to generate a fitting -abstract. But it can be interesting to define an own abstract that will -only be displayed on the external network. This is done with the -[abstract]-element. Example: +Friendica is using a semi intelligent mechanism to generate a fitting abstract. But it can be interesting to define an own abstract that will only be displayed on the external network. This is done with the [abstract]-element. Example: <pre>[abstract]Totally interesting! A must-see! Please click the link![/abstract] I want to tell you a really boring story that you really never wanted to hear.</pre> -Twitter would display the text "Totally interesting! A must-see! Please -click the link!". On Friendica you would only see the text after "I -want to tell you a really ..." +Twitter would display the text "Totally interesting! A must-see! Please click the link!". On Friendica you would only see the text after "I want to tell you a really ..." It is even possible to define abstracts for separate networks: @@ -176,17 +169,11 @@ that I wanted to share with you.[abstract] Today I was in the woods and took some real cool pictures ... </pre> -For Twitter and App.net the system will use the defined abstracts. For -other networks (e.g. when you are using the Statusnet connector) the -general abstract element will be used. +For Twitter and App.net the system will use the defined abstracts. For other networks (e.g. when you are using the Statusnet connector) the general abstract element will be used. -If you use (for example) the "buffer" connector to post to Facebook or -Google+ you can use this element to define an abstract for a longer -blogpost that you don't want to post completely to these networks. +If you use (for example) the "buffer" connector to post to Facebook or Google+ you can use this element to define an abstract for a longer blogpost that you don't want to post completely to these networks. -Networks like Facebook or Google+ aren't length limited. For this reason -the [abstract] element isn't used. Instead you have to name the explicit -network: +Networks like Facebook or Google+ aren't length limited. For this reason the [abstract] element isn't used. Instead you have to name the explicit network: <pre> [abstract]These days I had a strange encounter ...[abstract] @@ -196,9 +183,7 @@ newest blog post![abstract] really cool.[abstract] While taking pictures in the woods I had a really strange encounter ... </pre> -The [abstract] element isn't working with the native OStatus connection -or with connectors where we post the HTML. (Like Tumblr, Wordpress or -Pump.io) +The [abstract] element isn't working with the native OStatus connection or with connectors where we post the HTML. (Like Tumblr, Wordpress or Pump.io) Special ------- diff --git a/doc/de/BBCode.md b/doc/de/BBCode.md index 0856c30e50..d299072d61 100644 --- a/doc/de/BBCode.md +++ b/doc/de/BBCode.md @@ -131,8 +131,7 @@ Außerdem kann *url* die genaue url zu einer ogg Datei sein, die dann per H <pre>[url]*url*[/url]</pre> -Wenn *url* entweder oembed oder opengraph unterstützt wird das eingebettete -Objekt (z.B. ein Dokument von scribd) eingebunden. +Wenn *url* entweder oembed oder opengraph unterstützt wird das eingebettete Objekt (z.B. ein Dokument von scribd) eingebunden. Der Titel der Seite mit einem Link zur *url* wird ebenfalls angezeigt. Um eine Karte in einen Beitrag einzubinden, muss das *openstreetmap* Addon aktiviert werden. Ist dies der Fall, kann mit @@ -148,25 +147,17 @@ oder eine Adresse in obiger Form verwendet werden. Zusammenfassung für längere Beiträge ------------------------------------ -Wenn man seine Beiträge über mehrere Netzwerke verbreiten möchte, hat -man häufig das Problem, dass diese Netzwerke z.B. eine -Längenbeschränkung haben. (Z.B. Twitter). +Wenn man seine Beiträge über mehrere Netzwerke verbreiten möchte, hat man häufig das Problem, dass diese Netzwerke z.B. eine Längenbeschränkung haben. (Z.B. Twitter). -Friendica benutzt zum Erzeugen eines Anreißtextes eine halbwegs -intelligente Logik. Es kann aber dennoch von Interesse sein, eine eigene -Zusammenfassung zu erstellen, die nur auf dem Fremdnetzwerk dargestellt -wird. Dies geschieht mit dem [abstract]-Element. Beispiel: +Friendica benutzt zum Erzeugen eines Anreißtextes eine halbwegs intelligente Logik. Es kann aber dennoch von Interesse sein, eine eigene Zusammenfassung zu erstellen, die nur auf dem Fremdnetzwerk dargestellt wird. Dies geschieht mit dem [abstract]-Element. Beispiel: <pre>[abstract]Total spannend! Unbedingt diesen Link anklicken![/abstract] Hier erzähle ich euch eine total langweilige Geschichte, die ihr noch nie hören wolltet.</pre> -Auf Twitter würde das "Total spannend! Unbedingt diesen Link anklicken!" -stehen, auf Friendica würde nur der Text nach "Hier erzähle ..." -erscheinen. +Auf Twitter würde das "Total spannend! Unbedingt diesen Link anklicken!" stehen, auf Friendica würde nur der Text nach "Hier erzähle ..." erscheinen. -Es ist sogar möglich, für einzelne Netzwerke eigene Zusammenfassungen zu -erstellen: +Es ist sogar möglich, für einzelne Netzwerke eigene Zusammenfassungen zu erstellen: <pre> [abstract]Hallo Leute, hier meine neuesten Bilder![abstract] @@ -175,20 +166,11 @@ erstellen: Ich war heute wieder im Wald unterwegs und habe tolle Bilder geschossen ... </pre> -Für Twitter und App.net nimmt das System die entsprechenden Texte. Bei -anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim -Statusnet-Connector) wird dann die Zusammenfassung unter [abstract] -verwendet. +Für Twitter und App.net nimmt das System die entsprechenden Texte. Bei anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim Statusnet-Connector) wird dann die Zusammenfassung unter [abstract] verwendet. -Wenn man z.B. den "buffer"-Connector verwendet, um nach Facebook oder -Google+ zu posten, kann man dieses Element ebenfalls verwenden, wenn man -z.B. einen längeren Blogbeitrag erstellt hat, aber ihn nicht komplett in -diese Netzwerke posten möchte. +Wenn man z.B. den "buffer"-Connector verwendet, um nach Facebook oder Google+ zu posten, kann man dieses Element ebenfalls verwenden, wenn man z.B. einen längeren Blogbeitrag erstellt hat, aber ihn nicht komplett in diese Netzwerke posten möchte. -Netzwerke wie Facebook oder Google+ sind nicht in der Postinglänge -beschränkt. Aus diesem Grund greift nicht die -[abstract]-Zusammenfassung. Stattdessen muss man das Netzwerk explizit -angeben: +Netzwerke wie Facebook oder Google+ sind nicht in der Postinglänge beschränkt. Aus diesem Grund greift nicht die [abstract]-Zusammenfassung. Stattdessen muss man das Netzwerk explizit angeben: <pre> [abstract]Ich habe neulich wieder etwas erlebt, was ich euch mitteilen möchte.[abstract] @@ -199,9 +181,7 @@ erlebt, was ich euch mitteilen möchte.[abstract] Beim Bildermachen im Wald habe ich neulich eine interessante Person getroffen ... </pre> -Das [abstract]-Element greift nicht bei der nativen OStatus-Verbindung -oder bei Connectoren, die den HTML-Text posten wie z.B. die Connectoren -zu Tumblr, Wordpress oder Pump.io. +Das [abstract]-Element greift nicht bei der nativen OStatus-Verbindung oder bei Connectoren, die den HTML-Text posten wie z.B. die Connectoren zu Tumblr, Wordpress oder Pump.io. Spezielle Tags ------- @@ -209,5 +189,3 @@ Spezielle Tags Wenn Du über BBCode Tags in einer Nachricht schreiben möchtest, kannst Du [noparse], [nobb] oder [pre] verwenden um den BBCode Tags vor der Evaluierung zu schützen: <pre>[noparse][b]fett[/b][/noparse]</pre> : [b]fett[/b] - - From 8369e6ce596b766745ccae3e70b74d2b0a6b50ae Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 24 Feb 2016 07:37:18 +0100 Subject: [PATCH 127/273] Clarification between statusnet and GNU Social. --- doc/BBCode.md | 2 +- doc/de/BBCode.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/BBCode.md b/doc/BBCode.md index 75cad5d96f..5aa33fd81b 100644 --- a/doc/BBCode.md +++ b/doc/BBCode.md @@ -169,7 +169,7 @@ that I wanted to share with you.[abstract] Today I was in the woods and took some real cool pictures ... </pre> -For Twitter and App.net the system will use the defined abstracts. For other networks (e.g. when you are using the Statusnet connector) the general abstract element will be used. +For Twitter and App.net the system will use the defined abstracts. For other networks (e.g. when you are using the "statusnet" connector that is used to post to GNU Social) the general abstract element will be used. If you use (for example) the "buffer" connector to post to Facebook or Google+ you can use this element to define an abstract for a longer blogpost that you don't want to post completely to these networks. diff --git a/doc/de/BBCode.md b/doc/de/BBCode.md index d299072d61..5f24fa2b61 100644 --- a/doc/de/BBCode.md +++ b/doc/de/BBCode.md @@ -166,7 +166,7 @@ Es ist sogar möglich, für einzelne Netzwerke eigene Zusammenfassungen zu erste Ich war heute wieder im Wald unterwegs und habe tolle Bilder geschossen ... </pre> -Für Twitter und App.net nimmt das System die entsprechenden Texte. Bei anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim Statusnet-Connector) wird dann die Zusammenfassung unter [abstract] verwendet. +Für Twitter und App.net nimmt das System die entsprechenden Texte. Bei anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim "statusnet"-Connector, der für das Posten nach GNU Social verwendet wird) wird dann die Zusammenfassung unter [abstract] verwendet. Wenn man z.B. den "buffer"-Connector verwendet, um nach Facebook oder Google+ zu posten, kann man dieses Element ebenfalls verwenden, wenn man z.B. einen längeren Blogbeitrag erstellt hat, aber ihn nicht komplett in diese Netzwerke posten möchte. From 68d7beb2981c22e4251542c835fac0ac40421390 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 24 Feb 2016 07:43:13 +0100 Subject: [PATCH 128/273] Welcome to the live editing and commenting of the pull request ;-) --- doc/BBCode.md | 18 +++++++++++++----- doc/de/BBCode.md | 14 ++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/doc/BBCode.md b/doc/BBCode.md index 5aa33fd81b..803f716aeb 100644 --- a/doc/BBCode.md +++ b/doc/BBCode.md @@ -148,15 +148,20 @@ This require "openstreetmap" addon version 1.3 or newer. Abstract for longer posts ------------------------- -If you want to spread your post to several third party networks you can have the problem that these networks have (for example) a length limitation. (Like on Twitter) +If you want to spread your post to several third party networks you can have the problem that these networks have (for example) a length limitation. +(Like on Twitter) -Friendica is using a semi intelligent mechanism to generate a fitting abstract. But it can be interesting to define an own abstract that will only be displayed on the external network. This is done with the [abstract]-element. Example: +Friendica is using a semi intelligent mechanism to generate a fitting abstract. +But it can be interesting to define an own abstract that will only be displayed on the external network. +This is done with the [abstract]-element. +Example: <pre>[abstract]Totally interesting! A must-see! Please click the link![/abstract] I want to tell you a really boring story that you really never wanted to hear.</pre> -Twitter would display the text "Totally interesting! A must-see! Please click the link!". On Friendica you would only see the text after "I want to tell you a really ..." +Twitter would display the text "Totally interesting! A must-see! Please click the link!". +On Friendica you would only see the text after "I want to tell you a really ..." It is even possible to define abstracts for separate networks: @@ -173,7 +178,9 @@ For Twitter and App.net the system will use the defined abstracts. For other net If you use (for example) the "buffer" connector to post to Facebook or Google+ you can use this element to define an abstract for a longer blogpost that you don't want to post completely to these networks. -Networks like Facebook or Google+ aren't length limited. For this reason the [abstract] element isn't used. Instead you have to name the explicit network: +Networks like Facebook or Google+ aren't length limited. +For this reason the [abstract] element isn't used. +Instead you have to name the explicit network: <pre> [abstract]These days I had a strange encounter ...[abstract] @@ -183,7 +190,8 @@ newest blog post![abstract] really cool.[abstract] While taking pictures in the woods I had a really strange encounter ... </pre> -The [abstract] element isn't working with the native OStatus connection or with connectors where we post the HTML. (Like Tumblr, Wordpress or Pump.io) +The [abstract] element isn't working with the native OStatus connection or with connectors where we post the HTML. +(Like Tumblr, Wordpress or Pump.io) Special ------- diff --git a/doc/de/BBCode.md b/doc/de/BBCode.md index 5f24fa2b61..cd9fa7673e 100644 --- a/doc/de/BBCode.md +++ b/doc/de/BBCode.md @@ -147,9 +147,13 @@ oder eine Adresse in obiger Form verwendet werden. Zusammenfassung für längere Beiträge ------------------------------------ -Wenn man seine Beiträge über mehrere Netzwerke verbreiten möchte, hat man häufig das Problem, dass diese Netzwerke z.B. eine Längenbeschränkung haben. (Z.B. Twitter). +Wenn man seine Beiträge über mehrere Netzwerke verbreiten möchte, hat man häufig das Problem, dass diese Netzwerke z.B. eine Längenbeschränkung haben. +(Z.B. Twitter). -Friendica benutzt zum Erzeugen eines Anreißtextes eine halbwegs intelligente Logik. Es kann aber dennoch von Interesse sein, eine eigene Zusammenfassung zu erstellen, die nur auf dem Fremdnetzwerk dargestellt wird. Dies geschieht mit dem [abstract]-Element. Beispiel: +Friendica benutzt zum Erzeugen eines Anreißtextes eine halbwegs intelligente Logik. +Es kann aber dennoch von Interesse sein, eine eigene Zusammenfassung zu erstellen, die nur auf dem Fremdnetzwerk dargestellt wird. +Dies geschieht mit dem [abstract]-Element. +Beispiel: <pre>[abstract]Total spannend! Unbedingt diesen Link anklicken![/abstract] Hier erzähle ich euch eine total langweilige Geschichte, die ihr noch @@ -166,11 +170,13 @@ Es ist sogar möglich, für einzelne Netzwerke eigene Zusammenfassungen zu erste Ich war heute wieder im Wald unterwegs und habe tolle Bilder geschossen ... </pre> -Für Twitter und App.net nimmt das System die entsprechenden Texte. Bei anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim "statusnet"-Connector, der für das Posten nach GNU Social verwendet wird) wird dann die Zusammenfassung unter [abstract] verwendet. +Für Twitter und App.net nimmt das System die entsprechenden Texte. +Bei anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim "statusnet"-Connector, der für das Posten nach GNU Social verwendet wird) wird dann die Zusammenfassung unter [abstract] verwendet. Wenn man z.B. den "buffer"-Connector verwendet, um nach Facebook oder Google+ zu posten, kann man dieses Element ebenfalls verwenden, wenn man z.B. einen längeren Blogbeitrag erstellt hat, aber ihn nicht komplett in diese Netzwerke posten möchte. -Netzwerke wie Facebook oder Google+ sind nicht in der Postinglänge beschränkt. Aus diesem Grund greift nicht die [abstract]-Zusammenfassung. Stattdessen muss man das Netzwerk explizit angeben: +Netzwerke wie Facebook oder Google+ sind nicht in der Postinglänge beschränkt. +Aus diesem Grund greift nicht die [abstract]-Zusammenfassung. Stattdessen muss man das Netzwerk explizit angeben: <pre> [abstract]Ich habe neulich wieder etwas erlebt, was ich euch mitteilen möchte.[abstract] From 1b14b22b76e26bd5bdee728321dd79f6c8b9913b Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Wed, 24 Feb 2016 20:46:06 +0100 Subject: [PATCH 129/273] Client section in DeveloperIntro docs --- doc/Developers-Intro.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/Developers-Intro.md b/doc/Developers-Intro.md index 10bbd5632a..8e3cd03b18 100644 --- a/doc/Developers-Intro.md +++ b/doc/Developers-Intro.md @@ -83,11 +83,11 @@ Ask us to find out whom to talk to about their experiences. Do not worry about cross-posting. ###Client software -There are free software clients that do somehow work with Friendica but most of them need love and maintenance. -Also, they were mostly made for other platforms using the GNU Social API. -This means they lack the features that are really specific to Friendica. -Popular clients you might want to have a look at are: +As Friendica is using a [Twitter/GNU Social compatible API](help/api) any of the clients for those platforms should work with Friendica as well. +Furthermore there are several client projects, especially for use with Friendica. +If you are interested in improving those clients, please contact the developers of the clients directly. -* [Hotot (Linux)](http://hotot.org/) - abandoned -* [Friendica for Android](https://github.com/max-weller/friendica-for-android) - abandoned -* You can find more working client software in [Wikipedia](https://en.wikipedia.org/wiki/Friendica). +* Android / CynogenMod: **Friendica for Android** [src](https://github.com/max-weller/friendica-for-android), [homepage](http://friendica.android.max-weller.de/) - abandoned +* iOS: *currently no client* +* SailfishOS: **Friendiy** [src](https://kirgroup.com/projects/fabrixxm/harbour-friendly) - developed by [Fabio](https://kirgroup.com/profile/fabrixxm/?tab=profile) +* Windows: **Friendica Mobile** for Windows versions [before 8.1](http://windowsphone.com/s?appid=e3257730-c9cf-4935-9620-5261e3505c67) and [Windows 10](https://www.microsoft.com/store/apps/9nblggh0fhmn) - developed by [Gerhard Seeber](http://mozartweg.dyndns.org/friendica/profile/gerhard/?tab=profile) From f4b0e9316eb77ac1acd28bc34ccf87bca9283059 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Thu, 25 Feb 2016 21:37:37 +0100 Subject: [PATCH 130/273] a missing break --- doc/BBCode.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/BBCode.md b/doc/BBCode.md index 803f716aeb..186b1cda93 100644 --- a/doc/BBCode.md +++ b/doc/BBCode.md @@ -174,7 +174,8 @@ that I wanted to share with you.[abstract] Today I was in the woods and took some real cool pictures ... </pre> -For Twitter and App.net the system will use the defined abstracts. For other networks (e.g. when you are using the "statusnet" connector that is used to post to GNU Social) the general abstract element will be used. +For Twitter and App.net the system will use the defined abstracts. +For other networks (e.g. when you are using the "statusnet" connector that is used to post to GNU Social) the general abstract element will be used. If you use (for example) the "buffer" connector to post to Facebook or Google+ you can use this element to define an abstract for a longer blogpost that you don't want to post completely to these networks. From a3960bda371c8083bad2ed422169282fed004d24 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 27 Feb 2016 23:54:17 +0100 Subject: [PATCH 131/273] New Diaspora code --- include/diaspora.php | 37 +++ include/diaspora2.php | 638 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 675 insertions(+) create mode 100644 include/diaspora2.php diff --git a/include/diaspora.php b/include/diaspora.php index 93fe2a472f..9dbbeabbcf 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -58,6 +58,8 @@ function diaspora_dispatch($importer,$msg,$attempt=1) { return; } + $data = $msg; + // php doesn't like dashes in variable names $msg['message'] = str_replace( @@ -74,48 +76,83 @@ function diaspora_dispatch($importer,$msg,$attempt=1) { if($xmlbase->request) { + $tempfile = tempnam(get_temppath(), "diaspora-request"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_request($importer,$xmlbase->request); } elseif($xmlbase->status_message) { + //$tempfile = tempnam(get_temppath(), "diaspora-status_message"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_post($importer,$xmlbase->status_message,$msg); } elseif($xmlbase->profile) { + //$tempfile = tempnam(get_temppath(), "diaspora-profile"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_profile($importer,$xmlbase->profile,$msg); } elseif($xmlbase->comment) { + //$tempfile = tempnam(get_temppath(), "diaspora-comment"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_comment($importer,$xmlbase->comment,$msg); } elseif($xmlbase->like) { + //$tempfile = tempnam(get_temppath(), "diaspora-like"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_like($importer,$xmlbase->like,$msg); } elseif($xmlbase->asphoto) { + $tempfile = tempnam(get_temppath(), "diaspora-asphoto"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg); } elseif($xmlbase->reshare) { + //$tempfile = tempnam(get_temppath(), "diaspora-reshare"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg); } elseif($xmlbase->retraction) { + $tempfile = tempnam(get_temppath(), "diaspora-retraction"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); } elseif($xmlbase->signed_retraction) { + $tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); } elseif($xmlbase->relayable_retraction) { + //$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg); } elseif($xmlbase->photo) { + //$tempfile = tempnam(get_temppath(), "diaspora-photo"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_photo($importer,$xmlbase->photo,$msg,$attempt); } elseif($xmlbase->conversation) { + $tempfile = tempnam(get_temppath(), "diaspora-conversation"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_conversation($importer,$xmlbase->conversation,$msg); } elseif($xmlbase->message) { + $tempfile = tempnam(get_temppath(), "diaspora-message"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_message($importer,$xmlbase->message,$msg); } elseif($xmlbase->participation) { + //$tempfile = tempnam(get_temppath(), "diaspora-participation"); + //file_put_contents($tempfile, json_encode($data)); + $ret = diaspora_participation($importer,$xmlbase->participation); + } + elseif($xmlbase->poll_participation) { + $tempfile = tempnam(get_temppath(), "diaspora-poll_participation"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_participation($importer,$xmlbase->participation); } else { + $tempfile = tempnam(get_temppath(), "diaspora-unknown"); + file_put_contents($tempfile, json_encode($data)); logger('diaspora_dispatch: unknown message type: ' . print_r($xmlbase,true)); } return $ret; diff --git a/include/diaspora2.php b/include/diaspora2.php new file mode 100644 index 0000000000..690e54aa41 --- /dev/null +++ b/include/diaspora2.php @@ -0,0 +1,638 @@ +<?php +/** + * @file include/diaspora.php + * @brief The implementation of the diaspora protocol + */ + +require_once("include/diaspora.php"); +require_once("include/Scrape.php"); + +function array_to_xml($array, &$xml) { + + if (!is_object($xml)) { + foreach($array as $key => $value) { + $root = new SimpleXMLElement('<'.$key.'/>'); + array_to_xml($value, $root); + + $dom = dom_import_simplexml($root)->ownerDocument; + $dom->formatOutput = true; + return $dom->saveXML(); + } + } + + foreach($array as $key => $value) { + if (!is_array($value) AND !is_numeric($key)) + $xml->addChild($key, $value); + elseif (is_array($value)) + array_to_xml($value, $xml->addChild($key)); + } +} + +/** + * @brief This class contain functions to create and send DFRN XML files + * + */ +class diaspora { + + public static function dispatch_public($msg) { + + $enabled = intval(get_config("system", "diaspora_enabled")); + if (!$enabled) { + logger('diaspora is disabled'); + return false; + } + + // Use a dummy importer to import the data for the public copy + $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); + self::dispatch($importer,$msg); + + // 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') + AND NOT `account_expired` AND NOT `account_removed`", + dbesc(NETWORK_DIASPORA), + dbesc($msg["author"]) + ); + if(count($r)) { + foreach($r as $rr) { + logger("delivering to: ".$rr["username"]); + self::dispatch($rr,$msg); + } + } else + logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); + } + + public static function dispatch($importer, $msg) { + + // 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 (!diaspora::valid_posting($msg, $fields)) { + logger("Invalid posting"); + return false; + } + + $type = $fields->getName(); + + switch ($type) { + case "account_deletion": + return self::import_account_deletion($importer, $fields); + + case "comment": + return self::import_comment($importer, $sender, $fields); + + case "conversation": + return self::import_conversation($importer, $fields); + + case "like": + return self::import_like($importer, $sender, $fields); + + case "message": + return self::import_message($importer, $fields); + + case "participation": + return self::import_participation($importer, $fields); + + case "photo": + return self::import_photo($importer, $fields); + + case "poll_participation": + return self::import_poll_participation($importer, $fields); + + case "profile": + return self::import_profile($importer, $fields); + + case "request": + return self::import_request($importer, $fields); + + case "reshare": + return self::import_reshare($importer, $fields); + + case "retraction": + return self::import_retraction($importer, $fields); + + case "status_message": + return self::import_status_message($importer, $fields); + + default: + logger("Unknown message type ".$type); + return false; + } + + return true; + } + + /** + * @brief Checks if a posting is valid and fetches the data fields. + * + * This function does not only check the signature. + * 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 + * + * @return bool Is the posting valid? + */ + private function valid_posting($msg, &$fields) { + + $data = parse_xml_string($msg->message, false); + + $first_child = $data->getName(); + + if ($data->getName() == "XML") { + $oldXML = true; + foreach ($data->post->children() as $child) + $element = $child; + } else { + $oldXML = false; + $element = $data; + } + + $type = $element->getName(); + + if (in_array($type, array("signed_retraction", "relayable_retraction"))) + $type = "retraction"; + + $fields = new SimpleXMLElement("<".$type."/>"); + + $signed_data = ""; + + foreach ($element->children() AS $fieldname => $data) { + + if ($oldXML) { + // Translation for the old XML structure + 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") + $fieldname = "author"; + + if ($fieldname == "recipient_handle") + $fieldname = "recipient"; + + if ($fieldname == "root_diaspora_id") + $fieldname = "root_author"; + + if ($type == "retraction") { + if ($fieldname == "post_guid") + $fieldname = "target_guid"; + + if ($fieldname == "type") + $fieldname = "target_type"; + } + } + + if ($fieldname == "author_signature") + $author_signature = base64_decode($data); + elseif ($fieldname == "parent_author_signature") + $parent_author_signature = base64_decode($data); + elseif ($fieldname != "target_author_signature") { + if ($signed_data != "") { + $signed_data .= ";"; + $signed_data_parent .= ";"; + } + + $signed_data .= $data; + $fields->$fieldname = $data; + } + } + + if (in_array($type, array("status_message", "reshare"))) + if ($msg->author != $fields->author) { + logger("Message handle is not the same as envelope sender. Quitting this message."); + return false; + } + + if (!in_array($type, array("comment", "conversation", "message", "like"))) + return true; + + if (!isset($author_signature)) + return false; + + if (isset($parent_author_signature)) { + $key = self::get_key($msg->author); + + if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) + return false; + } + + $key = self::get_key($fields->author); + + return rsa_verify($signed_data, $author_signature, $key, "sha256"); + } + + private function get_key($handle) { + logger("Fetching diaspora key for: ".$handle); + + $r = self::get_person_by_handle($handle); + if($r) + return $r["pubkey"]; + + return ""; + } + + private function get_person_by_handle($handle) { + + $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", + dbesc(NETWORK_DIASPORA), + dbesc($handle) + ); + if (count($r)) { + $person = $r[0]; + logger("In cache ".print_r($r,true), LOGGER_DEBUG); + + // update record occasionally so it doesn't get stale + $d = strtotime($person["updated"]." +00:00"); + if ($d < strtotime("now - 14 days")) + $update = true; + } + + if (!$person OR $update) { + logger("create or refresh", LOGGER_DEBUG); + $r = probe_url($handle, PROBE_DIASPORA); + + // Note that Friendica contacts will return a "Diaspora person" + // if Diaspora connectivity is enabled on their server + if (count($r) AND ($r["network"] === NETWORK_DIASPORA)) { + self::add_fcontact($r, $update); + $person = $r; + } + } + return $person; + } + + private function add_fcontact($arr, $update = false) { + /// @todo Remove this function from include/network.php + + if($update) { + $r = q("UPDATE `fcontact` SET + `name` = '%s', + `photo` = '%s', + `request` = '%s', + `nick` = '%s', + `addr` = '%s', + `batch` = '%s', + `notify` = '%s', + `poll` = '%s', + `confirm` = '%s', + `alias` = '%s', + `pubkey` = '%s', + `updated` = '%s' + WHERE `url` = '%s' AND `network` = '%s'", + dbesc($arr["name"]), + dbesc($arr["photo"]), + dbesc($arr["request"]), + dbesc($arr["nick"]), + dbesc($arr["addr"]), + dbesc($arr["batch"]), + dbesc($arr["notify"]), + dbesc($arr["poll"]), + dbesc($arr["confirm"]), + dbesc($arr["alias"]), + dbesc($arr["pubkey"]), + dbesc(datetime_convert()), + dbesc($arr["url"]), + dbesc($arr["network"]) + ); + } else { + $r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`, + `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`) + VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", + dbesc($arr["url"]), + dbesc($arr["name"]), + dbesc($arr["photo"]), + dbesc($arr["request"]), + dbesc($arr["nick"]), + dbesc($arr["addr"]), + dbesc($arr["batch"]), + dbesc($arr["notify"]), + dbesc($arr["poll"]), + dbesc($arr["confirm"]), + dbesc($arr["network"]), + dbesc($arr["alias"]), + dbesc($arr["pubkey"]), + dbesc(datetime_convert()) + ); + } + + return $r; + } + + private function get_contact_by_handle($uid, $handle) { + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", + intval($uid), + dbesc($handle) + ); + + if ($r AND count($r)) + return $r[0]; + + $handle_parts = explode("@", $handle); + $nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0]; + $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1", + dbesc(NETWORK_DFRN), + intval($uid), + dbesc($nurl_sql) + ); + if($r AND count($r)) + return $r[0]; + + return false; + } + +/* +function DiasporaFetchGuid($item) { + preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", + function ($match) use ($item){ + return(DiasporaFetchGuidSub($match, $item)); + },$item["body"]); +} + +function DiasporaFetchGuidSub($match, $item) { + $a = get_app(); + + if (!diaspora_store_by_guid($match[1], $item["author-link"])) + diaspora_store_by_guid($match[1], $item["owner-link"]); +} + +function diaspora_store_by_guid($guid, $server, $uid = 0) { + require_once("include/Contact.php"); + + $serverparts = parse_url($server); + $server = $serverparts["scheme"]."://".$serverparts["host"]; + + logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); + + $item = diaspora_fetch_message($guid, $server); + + if (!$item) + return false; + + logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); + + $body = $item["body"]; + $str_tags = $item["tag"]; + $app = $item["app"]; + $created = $item["created"]; + $author = $item["author"]; + $guid = $item["guid"]; + $private = $item["private"]; + $object = $item["object"]; + $objecttype = $item["object-type"]; + + $message_id = $author.':'.$guid; + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), + dbesc($guid) + ); + if(count($r)) + return $r[0]["id"]; + + $person = find_diaspora_person_by_handle($author); + + $contact_id = get_contact($person['url'], $uid); + + $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id)); + $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid)); + + if ($contacts AND $importers) + if(!diaspora_post_allow($importers[0],$contacts[0], false)) { + logger('Ignoring author '.$person['url'].' for uid '.$uid); + return false; + } else + logger('Author '.$person['url'].' is allowed for uid '.$uid); + + $datarray = array(); + $datarray['uid'] = $uid; + $datarray['contact-id'] = $contact_id; + $datarray['wall'] = 0; + $datarray['network'] = NETWORK_DIASPORA; + $datarray['guid'] = $guid; + $datarray['uri'] = $datarray['parent-uri'] = $message_id; + $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); + $datarray['private'] = $private; + $datarray['parent'] = 0; + $datarray['plink'] = diaspora_plink($author, $guid); + $datarray['author-name'] = $person['name']; + $datarray['author-link'] = $person['url']; + $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); + $datarray['owner-name'] = $datarray['author-name']; + $datarray['owner-link'] = $datarray['author-link']; + $datarray['owner-avatar'] = $datarray['author-avatar']; + $datarray['body'] = $body; + $datarray['tag'] = $str_tags; + $datarray['app'] = $app; + $datarray['visible'] = ((strlen($body)) ? 1 : 0); + $datarray['object'] = $object; + $datarray['object-type'] = $objecttype; + + if ($datarray['contact-id'] == 0) + return false; + + DiasporaFetchGuid($datarray); + $message_id = item_store($datarray); + + /// @TODO + /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post + + return $message_id; +} +*/ + + private function import_account_deletion($importer, $data) { + return true; + } + + private function import_comment($importer, $sender, $data) { + $guid = notags(unxmlify($data->guid)); + $parent_guid = notags(unxmlify($data->parent_guid)); + $text = unxmlify($data->text); + $author = notags(unxmlify($data->author)); + + $contact = self::get_contact_by_handle($importer["uid"], $sender); + if (!$contact) { + logger("cannot find contact for sender: ".$sender); + return false; + } +/* + if(! diaspora_post_allow($importer,$contact, true)) { + logger('diaspora_comment: Ignoring this author.'); + return 202; + } + + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer['uid']), + dbesc($guid) + ); + if(count($r)) { + logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid); + return; + } + + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer['uid']), + dbesc($parent_guid) + ); + + if(!count($r)) { + $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); + + if (!$result) { + $person = find_diaspora_person_by_handle($diaspora_handle); + $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); + } + + if ($result) { + logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG); + + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer['uid']), + dbesc($parent_guid) + ); + } + } + + if(! count($r)) { + logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid); + return; + } + $parent_item = $r[0]; + + // Find the original comment author information. + // We need this to make sure we display the comment author + // information (name and avatar) correctly. + if(strcasecmp($diaspora_handle,$msg['author']) == 0) + $person = $contact; + else { + $person = find_diaspora_person_by_handle($diaspora_handle); + + if(! is_array($person)) { + logger('diaspora_comment: unable to find author details'); + return; + } + } + + // Fetch the contact id - if we know this contact + $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc(normalise_link($person['url'])), intval($importer['uid'])); + if ($r) { + $cid = $r[0]['id']; + $network = $r[0]['network']; + } else { + $cid = $contact['id']; + $network = NETWORK_DIASPORA; + } + + $body = diaspora2bb($text); + $message_id = $diaspora_handle . ':' . $guid; + + $datarray = array(); + + $datarray['uid'] = $importer['uid']; + $datarray['contact-id'] = $cid; + $datarray['type'] = 'remote-comment'; + $datarray['wall'] = $parent_item['wall']; + $datarray['network'] = $network; + $datarray['verb'] = ACTIVITY_POST; + $datarray['gravity'] = GRAVITY_COMMENT; + $datarray['guid'] = $guid; + $datarray['uri'] = $message_id; + $datarray['parent-uri'] = $parent_item['uri']; + + // No timestamps for comments? OK, we'll the use current time. + $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert(); + $datarray['private'] = $parent_item['private']; + + $datarray['owner-name'] = $parent_item['owner-name']; + $datarray['owner-link'] = $parent_item['owner-link']; + $datarray['owner-avatar'] = $parent_item['owner-avatar']; + + $datarray['author-name'] = $person['name']; + $datarray['author-link'] = $person['url']; + $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); + $datarray['body'] = $body; + $datarray["object"] = json_encode($xml); + $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; + + // We can't be certain what the original app is if the message is relayed. + if(($parent_item['origin']) && (! $parent_author_signature)) + $datarray['app'] = 'Diaspora'; + + DiasporaFetchGuid($datarray); + $message_id = item_store($datarray); + + $datarray['id'] = $message_id; + + // If we are the origin of the parent we store the original signature and notify our followers + if($parent_item['origin']) { + $author_signature_base64 = base64_encode($author_signature); + $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle); + + q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", + intval($message_id), + dbesc($signed_data), + dbesc($author_signature_base64), + dbesc($diaspora_handle) + ); + + // notify others + proc_run('php','include/notifier.php','comment-import',$message_id); + } +*/ + return true; + } + + private function import_conversation($importer, $data) { + return true; + } + + private function import_like($importer, $sender, $data) { + return true; + } + + private function import_message($importer, $data) { + return true; + } + + private function import_participation($importer, $data) { + return true; + } + + private function import_photo($importer, $data) { + return true; + } + + private function import_poll_participation($importer, $data) { + return true; + } + + private function import_profile($importer, $data) { + return true; + } + + private function import_request($importer, $data) { + return true; + } + + private function import_reshare($importer, $data) { + return true; + } + + private function import_retraction($importer, $data) { + return true; + } + + private function import_status_message($importer, $data) { + return true; + } +} +?> From 08a780211211c79053a710866fa2057f648a6d51 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Sun, 28 Feb 2016 08:30:45 +0100 Subject: [PATCH 132/273] old location might vanish, vinzv moved the GS API docs over here --- doc/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.md b/doc/api.md index c020f403ff..bf287585d3 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,6 +1,6 @@ Friendica API === -The Friendica API aims to be compatible to the [GNU Social API](http://skilledtests.com/wiki/Twitter-compatible_API) and the [Twitter API](https://dev.twitter.com/rest/public). +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. From c02b54997e84667f195ee3ddf0c809a863fab294 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 28 Feb 2016 19:05:23 +0100 Subject: [PATCH 133/273] Like and Comment could work (partially) --- include/diaspora2.php | 589 ++++++++++++++++++++++++++---------------- 1 file changed, 372 insertions(+), 217 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 690e54aa41..e6e2d74bf6 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -4,8 +4,9 @@ * @brief The implementation of the diaspora protocol */ -require_once("include/diaspora.php"); +require_once("include/bb2diaspora.php"); require_once("include/Scrape.php"); +require_once("include/Contact.php"); function array_to_xml($array, &$xml) { @@ -66,7 +67,7 @@ class diaspora { // 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; + $sender = $msg["author"]; if (!diaspora::valid_posting($msg, $fields)) { logger("Invalid posting"); @@ -80,12 +81,14 @@ class diaspora { return self::import_account_deletion($importer, $fields); case "comment": - return self::import_comment($importer, $sender, $fields); + return true; + // return self::import_comment($importer, $sender, $fields); case "conversation": return self::import_conversation($importer, $fields); case "like": + // return true; return self::import_like($importer, $sender, $fields); case "message": @@ -136,7 +139,7 @@ class diaspora { */ private function valid_posting($msg, &$fields) { - $data = parse_xml_string($msg->message, false); + $data = parse_xml_string($msg["message"], false); $first_child = $data->getName(); @@ -202,12 +205,13 @@ class diaspora { } $signed_data .= $data; - $fields->$fieldname = $data; } + if (!in_array($fieldname, array("parent_author_signature", "target_author_signature"))) + $fields->$fieldname = $data; } if (in_array($type, array("status_message", "reshare"))) - if ($msg->author != $fields->author) { + if ($msg["author"] != $fields->author) { logger("Message handle is not the same as envelope sender. Quitting this message."); return false; } @@ -219,7 +223,7 @@ class diaspora { return false; if (isset($parent_author_signature)) { - $key = self::get_key($msg->author); + $key = self::get_key($msg["author"]); if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) return false; @@ -349,104 +353,166 @@ class diaspora { return false; } + private function fetch_guid($item) { + preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", + function ($match) use ($item){ + return(self::fetch_guid_sub($match, $item)); + },$item["body"]); + } + + private function fetch_guid_sub($match, $item) { + $a = get_app(); + + if (!self::store_by_guid($match[1], $item["author-link"])) + self::store_by_guid($match[1], $item["owner-link"]); + } + + private function store_by_guid($guid, $server, $uid = 0) { + $serverparts = parse_url($server); + $server = $serverparts["scheme"]."://".$serverparts["host"]; + + logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); + +/// @todo $item = self::fetch_message($guid, $server); + + if (!$item) + return false; + + logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); + +// @todo - neue Funktion import_status... nutzen +print_r($item); +die(); + return self::import_status_message($importer, $data); + /* -function DiasporaFetchGuid($item) { - preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", - function ($match) use ($item){ - return(DiasporaFetchGuidSub($match, $item)); - },$item["body"]); -} + $body = $item["body"]; + $str_tags = $item["tag"]; + $app = $item["app"]; + $created = $item["created"]; + $author = $item["author"]; + $guid = $item["guid"]; + $private = $item["private"]; + $object = $item["object"]; + $objecttype = $item["object-type"]; -function DiasporaFetchGuidSub($match, $item) { - $a = get_app(); + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), + dbesc($guid) + ); + if(count($r)) + return $r[0]["id"]; - if (!diaspora_store_by_guid($match[1], $item["author-link"])) - diaspora_store_by_guid($match[1], $item["owner-link"]); -} + $person = self::get_person_by_handle($author); -function diaspora_store_by_guid($guid, $server, $uid = 0) { - require_once("include/Contact.php"); + $contact_id = get_contact($person["url"], $uid); - $serverparts = parse_url($server); - $server = $serverparts["scheme"]."://".$serverparts["host"]; + $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id)); + $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid)); - logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); + if ($contacts AND $importers) + if (!self::post_allow($importers[0],$contacts[0], false)) { + logger("Ignoring author ".$person["url"]." for uid ".$uid); + return false; + } else + logger("Author ".$person["url"]." is allowed for uid ".$uid); - $item = diaspora_fetch_message($guid, $server); + $datarray = array(); + $datarray["uid"] = $uid; + $datarray["contact-id"] = $contact_id; + $datarray["wall"] = 0; + $datarray["network"] = NETWORK_DIASPORA; + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created); + $datarray["private"] = $private; + $datarray["parent"] = 0; + $datarray["plink"] = self::plink($author, $guid); + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]); + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; + $datarray["body"] = $body; + $datarray["tag"] = $str_tags; + $datarray["app"] = $app; + $datarray["visible"] = ((strlen($body)) ? 1 : 0); + $datarray["object"] = $object; + $datarray["object-type"] = $objecttype; - if (!$item) - return false; + if ($datarray["contact-id"] == 0) + return false; - logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); + self::fetch_guid($datarray); + $message_id = item_store($datarray); - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $created = $item["created"]; - $author = $item["author"]; - $guid = $item["guid"]; - $private = $item["private"]; - $object = $item["object"]; - $objecttype = $item["object-type"]; + /// @TODO + /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post - $message_id = $author.':'.$guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), - dbesc($guid) - ); - if(count($r)) - return $r[0]["id"]; - - $person = find_diaspora_person_by_handle($author); - - $contact_id = get_contact($person['url'], $uid); - - $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id)); - $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid)); - - if ($contacts AND $importers) - if(!diaspora_post_allow($importers[0],$contacts[0], false)) { - logger('Ignoring author '.$person['url'].' for uid '.$uid); - return false; - } else - logger('Author '.$person['url'].' is allowed for uid '.$uid); - - $datarray = array(); - $datarray['uid'] = $uid; - $datarray['contact-id'] = $contact_id; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = diaspora_plink($author, $guid); - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['owner-name'] = $datarray['author-name']; - $datarray['owner-link'] = $datarray['author-link']; - $datarray['owner-avatar'] = $datarray['author-avatar']; - $datarray['body'] = $body; - $datarray['tag'] = $str_tags; - $datarray['app'] = $app; - $datarray['visible'] = ((strlen($body)) ? 1 : 0); - $datarray['object'] = $object; - $datarray['object-type'] = $objecttype; - - if ($datarray['contact-id'] == 0) - return false; - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - /// @TODO - /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post - - return $message_id; -} + return $message_id; */ + } + + private function post_allow($importer, $contact, $is_comment = false) { + + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + // Normally this should have handled by getting a request - but this could get lost + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { + q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", + intval(CONTACT_IS_FRIEND), + intval($contact["id"]), + intval($importer["uid"]) + ); + $contact["rel"] = CONTACT_IS_FRIEND; + logger("defining user ".$contact["nick"]." as friend"); + } + + if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"])) + return false; + if($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) + return true; + + return false; + } + + private function fetch_parent_item($uid, $guid, $author, $contact) { + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `owner-name`, `owner-link`, `owner-avatar`, `origin` + FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), dbesc($guid)); + + if(!count($r)) { + $result = self::store_by_guid($guid, $contact["url"], $uid); + + if (!$result) { + $person = self::get_person_by_handle($author); + $result = self::store_by_guid($guid, $person["url"], $uid); + } + + if ($result) { + logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG); + + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, + `owner-name`, `owner-link`, `owner-avatar`, `origin` + FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), dbesc($guid)); + } + } + + if (!count($r)) { + logger("parent item not found: parent: ".$guid." item: ".$guid); + return false; + } else + return $r[0]; + } private function import_account_deletion($importer, $data) { return true; @@ -463,132 +529,93 @@ function diaspora_store_by_guid($guid, $server, $uid = 0) { logger("cannot find contact for sender: ".$sender); return false; } + + if (!self::post_allow($importer,$contact, true)) { + logger("Ignoring the author ".$sender); + return false; + } /* - if(! diaspora_post_allow($importer,$contact, true)) { - logger('diaspora_comment: Ignoring this author.'); - return 202; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid); - return; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - - if(!count($r)) { - $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); - - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); - } - - if ($result) { - logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG); - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - } - } - - if(! count($r)) { - logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid); - return; - } - $parent_item = $r[0]; - - // Find the original comment author information. - // We need this to make sure we display the comment author - // information (name and avatar) correctly. - if(strcasecmp($diaspora_handle,$msg['author']) == 0) - $person = $contact; - else { - $person = find_diaspora_person_by_handle($diaspora_handle); - - if(! is_array($person)) { - logger('diaspora_comment: unable to find author details'); - return; - } - } - - // Fetch the contact id - if we know this contact - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person['url'])), intval($importer['uid'])); - if ($r) { - $cid = $r[0]['id']; - $network = $r[0]['network']; - } else { - $cid = $contact['id']; - $network = NETWORK_DIASPORA; - } - - $body = diaspora2bb($text); - $message_id = $diaspora_handle . ':' . $guid; - - $datarray = array(); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $cid; - $datarray['type'] = 'remote-comment'; - $datarray['wall'] = $parent_item['wall']; - $datarray['network'] = $network; - $datarray['verb'] = ACTIVITY_POST; - $datarray['gravity'] = GRAVITY_COMMENT; - $datarray['guid'] = $guid; - $datarray['uri'] = $message_id; - $datarray['parent-uri'] = $parent_item['uri']; - - // No timestamps for comments? OK, we'll the use current time. - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert(); - $datarray['private'] = $parent_item['private']; - - $datarray['owner-name'] = $parent_item['owner-name']; - $datarray['owner-link'] = $parent_item['owner-link']; - $datarray['owner-avatar'] = $parent_item['owner-avatar']; - - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['body'] = $body; - $datarray["object"] = json_encode($xml); - $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; - - // We can't be certain what the original app is if the message is relayed. - if(($parent_item['origin']) && (! $parent_author_signature)) - $datarray['app'] = 'Diaspora'; - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - $datarray['id'] = $message_id; - - // If we are the origin of the parent we store the original signature and notify our followers - if($parent_item['origin']) { - $author_signature_base64 = base64_encode($author_signature); - $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle); - - q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($message_id), - dbesc($signed_data), - dbesc($author_signature_base64), - dbesc($diaspora_handle) - ); - - // notify others - proc_run('php','include/notifier.php','comment-import',$message_id); - } + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if(count($r)) { + logger("The comment already exists: ".$guid); + return; + } */ - return true; + $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; + + $person = self::get_person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author details"); + return false; + } + + // Fetch the contact id - if we know this contact + $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc(normalise_link($person["url"])), intval($importer["uid"])); + if ($r) { + $cid = $r[0]["id"]; + $network = $r[0]["network"]; + } else { + $cid = $contact["id"]; + $network = NETWORK_DIASPORA; + } + + $body = diaspora2bb($text); + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $cid; + $datarray["type"] = 'remote-comment'; + $datarray["wall"] = $parent_item["wall"]; + $datarray["network"] = $network; + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_COMMENT; + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; + $datarray["parent-uri"] = $parent_item["uri"]; + + // The old Diaspora protocol doesn't have a timestamp for comments + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert(); + $datarray["private"] = $parent_item["private"]; + + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); + $datarray["body"] = $body; + $datarray["object"] = json_encode($data); + $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; + + self::fetch_guid($datarray); + +// $message_id = item_store($datarray); +print_r($datarray); + $datarray["id"] = $message_id; + + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($message_id), + dbesc(json_encode($data)) + ); + + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); + } + + return $message_id; } private function import_conversation($importer, $data) { @@ -596,6 +623,134 @@ function diaspora_store_by_guid($guid, $server, $uid = 0) { } private function import_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)); + + // likes on comments aren't supported by Diaspora - only on posts + if ($parent_type !== "Post") + return false; + + // "positive" = "false" doesn't seem to be supported by Diaspora + if ($positive === "false") { + logger("Received a like with positive set to 'false' - this shouldn't exist at all"); + return false; + } + + $contact = self::get_contact_by_handle($importer["uid"], $sender); + if (!$contact) { + logger("cannot find contact for sender: ".$sender); + return false; + } + + if (!self::post_allow($importer,$contact, true)) { + logger("Ignoring the author ".$sender); + return false; + } +/* + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if(count($r)) { + logger("The like already exists: ".$guid); + return false; + } +*/ + $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; + + $person = self::get_person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author details"); + return false; + } + + // Fetch the contact id - if we know this contact + $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc(normalise_link($person["url"])), intval($importer["uid"])); + if ($r) { + $cid = $r[0]["id"]; + $network = $r[0]["network"]; + } else { + $cid = $contact["id"]; + $network = NETWORK_DIASPORA; + } + +// ------------------------------------------------ + $objtype = ACTIVITY_OBJ_NOTE; + $link = xmlify('<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'."\n") ; + $parent_body = $parent_item["body"]; + + $obj = <<< EOT + + <object> + <type>$objtype</type> + <local>1</local> + <id>{$parent_item["uri"]}</id> + <link>$link</link> + <title></title> + <content>$parent_body</content> + </object> +EOT; + $bodyverb = t('%1$s likes %2$s\'s %3$s'); + + $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; + $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]"; + $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]"; + $body = sprintf($bodyverb, $ulink, $alink, $plink); +// ------------------------------------------------ + + $datarray = array(); + + $datarray["uri"] = $author.":".$guid; + $datarray["uid"] = $importer["uid"]; + $datarray["guid"] = $guid; + $datarray["network"] = $network; + $datarray["contact-id"] = $cid; + $datarray["type"] = "activity"; + $datarray["wall"] = $parent_item["wall"]; + $datarray["gravity"] = GRAVITY_LIKE; + $datarray["parent"] = $parent_item["id"]; + $datarray["parent-uri"] = $parent_item["uri"]; + + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); + + $datarray["body"] = $body; + $datarray["private"] = $parent_item["private"]; + $datarray["verb"] = ACTIVITY_LIKE; + $datarray["object-type"] = $objtype; + $datarray["object"] = $obj; + $datarray["visible"] = 1; + $datarray["unseen"] = 1; + $datarray["last-child"] = 0; + +print_r($datarray); +// $message_id = item_store($datarray); + + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($message_id), + dbesc(json_encode($data)) + ); + + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); + } + return true; } From 6d3581dac8dc5a27a18a39709fa3ae38c080c490 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 29 Feb 2016 08:02:50 +0100 Subject: [PATCH 134/273] "profile". "like" and "comment" could work, status messages only partly. --- include/diaspora.php | 4 +- include/diaspora2.php | 1055 +++++++++++++++++++++++++++++++++-------- 2 files changed, 856 insertions(+), 203 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 9dbbeabbcf..4ec7489ca4 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -116,8 +116,8 @@ function diaspora_dispatch($importer,$msg,$attempt=1) { $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); } elseif($xmlbase->signed_retraction) { - $tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); - file_put_contents($tempfile, json_encode($data)); + //$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); } elseif($xmlbase->relayable_retraction) { diff --git a/include/diaspora2.php b/include/diaspora2.php index e6e2d74bf6..578a496c0a 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -7,30 +7,43 @@ require_once("include/bb2diaspora.php"); require_once("include/Scrape.php"); require_once("include/Contact.php"); +require_once("include/Photo.php"); +require_once("include/socgraph.php"); -function array_to_xml($array, &$xml) { +class xml { + function from_array($array, &$xml) { + + if (!is_object($xml)) { + foreach($array as $key => $value) { + $root = new SimpleXMLElement('<'.$key.'/>'); + array_to_xml($value, $root); + + $dom = dom_import_simplexml($root)->ownerDocument; + $dom->formatOutput = true; + return $dom->saveXML(); + } + } - if (!is_object($xml)) { foreach($array as $key => $value) { - $root = new SimpleXMLElement('<'.$key.'/>'); - array_to_xml($value, $root); - - $dom = dom_import_simplexml($root)->ownerDocument; - $dom->formatOutput = true; - return $dom->saveXML(); + if (!is_array($value) AND !is_numeric($key)) + $xml->addChild($key, $value); + elseif (is_array($value)) + array_to_xml($value, $xml->addChild($key)); } } - foreach($array as $key => $value) { - if (!is_array($value) AND !is_numeric($key)) - $xml->addChild($key, $value); - elseif (is_array($value)) - array_to_xml($value, $xml->addChild($key)); + function copy(&$source, &$target, $elementname) { + if (count($source->children()) == 0) + $target->addChild($elementname, $source); + else { + $child = $target->addChild($elementname); + foreach ($source->children() AS $childfield => $childentry) + self::copy($childentry, $child, $childfield); + } } } - /** - * @brief This class contain functions to create and send DFRN XML files + * @brief This class contain functions to create and send Diaspora XML files * */ class diaspora { @@ -39,7 +52,7 @@ class diaspora { $enabled = intval(get_config("system", "diaspora_enabled")); if (!$enabled) { - logger('diaspora is disabled'); + logger("diaspora is disabled"); return false; } @@ -69,7 +82,7 @@ class diaspora { // This will often be different with relayed messages (for example "like" and "comment") $sender = $msg["author"]; - if (!diaspora::valid_posting($msg, $fields)) { + if (!diaspora::valid_posting($msg, $fields, $data2)) { logger("Invalid posting"); return false; } @@ -77,34 +90,36 @@ class diaspora { $type = $fields->getName(); switch ($type) { - case "account_deletion": + case "account_deletion": // Not implemented return self::import_account_deletion($importer, $fields); case "comment": return true; - // return self::import_comment($importer, $sender, $fields); + //return self::import_comment($importer, $sender, $fields); case "conversation": return self::import_conversation($importer, $fields); case "like": - // return true; - return self::import_like($importer, $sender, $fields); + return true; + //return self::import_like($importer, $sender, $fields); case "message": - return self::import_message($importer, $fields); + return true; + //return self::import_message($importer, $fields); - case "participation": + case "participation": // Not implemented return self::import_participation($importer, $fields); case "photo": return self::import_photo($importer, $fields); - case "poll_participation": + case "poll_participation": // Not implemented return self::import_poll_participation($importer, $fields); case "profile": - return self::import_profile($importer, $fields); + return true; + //return self::import_profile($importer, $fields); case "request": return self::import_request($importer, $fields); @@ -116,7 +131,7 @@ class diaspora { return self::import_retraction($importer, $fields); case "status_message": - return self::import_status_message($importer, $fields); + return self::import_status_message($importer, $fields, $msg, $data2); default: logger("Unknown message type ".$type); @@ -133,16 +148,20 @@ 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 + * @param object $fields SimpleXML object that contains the posting when it is valid * * @return bool Is the posting valid? */ - private function valid_posting($msg, &$fields) { + private function valid_posting($msg, &$fields, &$element) { $data = parse_xml_string($msg["message"], false); + if (!is_object($data)) + return false; + $first_child = $data->getName(); + // Is this the new or the old version? if ($data->getName() == "XML") { $oldXML = true; foreach ($data->post->children() as $child) @@ -154,6 +173,8 @@ class diaspora { $type = $element->getName(); + // All retractions are handled identically from now on. + // In the new version there will only be "retraction". if (in_array($type, array("signed_retraction", "relayable_retraction"))) $type = "retraction"; @@ -161,8 +182,7 @@ class diaspora { $signed_data = ""; - foreach ($element->children() AS $fieldname => $data) { - + foreach ($element->children() AS $fieldname => $entry) { if ($oldXML) { // Translation for the old XML structure if ($fieldname == "diaspora_handle") @@ -195,30 +215,33 @@ class diaspora { } if ($fieldname == "author_signature") - $author_signature = base64_decode($data); + $author_signature = base64_decode($entry); elseif ($fieldname == "parent_author_signature") - $parent_author_signature = base64_decode($data); + $parent_author_signature = base64_decode($entry); elseif ($fieldname != "target_author_signature") { if ($signed_data != "") { $signed_data .= ";"; $signed_data_parent .= ";"; } - $signed_data .= $data; + $signed_data .= $entry; } if (!in_array($fieldname, array("parent_author_signature", "target_author_signature"))) - $fields->$fieldname = $data; + xml::copy($entry, $fields, $fieldname); } - if (in_array($type, array("status_message", "reshare"))) + // This is something that shouldn't happen at all. + if (in_array($type, array("status_message", "reshare", "profile"))) if ($msg["author"] != $fields->author) { logger("Message handle is not the same as envelope sender. Quitting this message."); return false; } + // Only some message types have signatures. So we quit here for the other types. if (!in_array($type, array("comment", "conversation", "message", "like"))) return true; + // No author_signature? This is a must, so we quit. if (!isset($author_signature)) return false; @@ -373,85 +396,57 @@ class diaspora { logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); -/// @todo $item = self::fetch_message($guid, $server); + $msg = self::fetch_message($guid, $server); - if (!$item) + if (!$msg) return false; logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); -// @todo - neue Funktion import_status... nutzen -print_r($item); -die(); - return self::import_status_message($importer, $data); + // Now call the dispatcher + return self::dispatch_public($msg); + } -/* - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $created = $item["created"]; - $author = $item["author"]; - $guid = $item["guid"]; - $private = $item["private"]; - $object = $item["object"]; - $objecttype = $item["object-type"]; + private function fetch_message($guid, $server, $level = 0) { - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), - dbesc($guid) - ); - if(count($r)) - return $r[0]["id"]; - - $person = self::get_person_by_handle($author); - - $contact_id = get_contact($person["url"], $uid); - - $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id)); - $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid)); - - if ($contacts AND $importers) - if (!self::post_allow($importers[0],$contacts[0], false)) { - logger("Ignoring author ".$person["url"]." for uid ".$uid); - return false; - } else - logger("Author ".$person["url"]." is allowed for uid ".$uid); - - $datarray = array(); - $datarray["uid"] = $uid; - $datarray["contact-id"] = $contact_id; - $datarray["wall"] = 0; - $datarray["network"] = NETWORK_DIASPORA; - $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; - $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created); - $datarray["private"] = $private; - $datarray["parent"] = 0; - $datarray["plink"] = self::plink($author, $guid); - $datarray["author-name"] = $person["name"]; - $datarray["author-link"] = $person["url"]; - $datarray["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]); - $datarray["owner-name"] = $datarray["author-name"]; - $datarray["owner-link"] = $datarray["author-link"]; - $datarray["owner-avatar"] = $datarray["author-avatar"]; - $datarray["body"] = $body; - $datarray["tag"] = $str_tags; - $datarray["app"] = $app; - $datarray["visible"] = ((strlen($body)) ? 1 : 0); - $datarray["object"] = $object; - $datarray["object-type"] = $objecttype; - - if ($datarray["contact-id"] == 0) + if ($level > 5) return false; - self::fetch_guid($datarray); - $message_id = item_store($datarray); + // This will not work if the server is not a Diaspora server + $source_url = $server."/p/".$guid.".xml"; + $x = fetch_url($source_url); + if(!$x) + return false; - /// @TODO - /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post + /// @todo - should maybe solved by the dispatcher + $source_xml = parse_xml_string($x, false); - return $message_id; -*/ + if (!is_object($source_xml)) + return false; + + if ($source_xml->post->reshare) { + // Reshare of a reshare - old Diaspora version + return self::fetch_message($source_xml->post->reshare->root_guid, $server, ++$level); + } elseif ($source_xml->getName() == "reshare") { + // Reshare of a reshare - new Diaspora version + return self::fetch_message($source_xml->root_guid, $server, ++$level); + } + + // Fetch the author - for the old and the new Diaspora version + if ($source_xml->post->status_message->diaspora_handle) + $author = (string)$source_xml->post->status_message->diaspora_handle; + elseif ($source_xml->author) + $author = (string)$source_xml->author; + + if (!$author) + return false; + + $msg = array("message" => $x, "author" => $author); + + // We don't really need this, but until the work is unfinished we better will keep this + $msg["key"] = self::get_key($msg["author"]); + + return $msg; } private function post_allow($importer, $contact, $is_comment = false) { @@ -485,7 +480,9 @@ die(); } private function fetch_parent_item($uid, $guid, $author, $contact) { - $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `owner-name`, `owner-link`, `owner-avatar`, `origin` + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, + `author-name`, `author-link`, `author-avatar`, + `owner-name`, `owner-link`, `owner-avatar` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($uid), dbesc($guid)); @@ -500,8 +497,9 @@ die(); if ($result) { logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG); - $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, - `owner-name`, `owner-link`, `owner-avatar`, `origin` + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, + `author-name`, `author-link`, `author-avatar`, + `owner-name`, `owner-link`, `owner-avatar` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($uid), dbesc($guid)); } @@ -514,7 +512,49 @@ die(); return $r[0]; } + private function get_author_contact_by_url($contact, $person, $uid) { + + $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc(normalise_link($person["url"])), intval($uid)); + if ($r) { + $cid = $r[0]["id"]; + $network = $r[0]["network"]; + } else { + $cid = $contact["id"]; + $network = NETWORK_DIASPORA; + } + + return (array("cid" => $cid, "network" => $network)); + } + + public static function is_redmatrix($url) { + return(strstr($url, "/channel/")); + } + + private function plink($addr, $guid) { + $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); + + // Fallback + if (!$r) + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; + + // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table + // So we try another way as well. + $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); + if ($s) + $r[0]["network"] = $s[0]["network"]; + + if ($r[0]["network"] == NETWORK_DFRN) + return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); + + if (self::is_redmatrix($r[0]["url"])) + return $r[0]["url"]."/?f=&mid=".$guid; + + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; + } + private function import_account_deletion($importer, $data) { + // Not supported by now. We are waiting for sample data return true; } @@ -534,16 +574,16 @@ die(); logger("Ignoring the author ".$sender); return false; } -/* + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer["uid"]), dbesc($guid) ); if(count($r)) { logger("The comment already exists: ".$guid); - return; + return false; } -*/ + $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) return false; @@ -555,51 +595,39 @@ die(); } // Fetch the contact id - if we know this contact - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person["url"])), intval($importer["uid"])); - if ($r) { - $cid = $r[0]["id"]; - $network = $r[0]["network"]; - } else { - $cid = $contact["id"]; - $network = NETWORK_DIASPORA; - } - - $body = diaspora2bb($text); + $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]); $datarray = array(); $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $cid; - $datarray["type"] = 'remote-comment'; - $datarray["wall"] = $parent_item["wall"]; - $datarray["network"] = $network; - $datarray["verb"] = ACTIVITY_POST; - $datarray["gravity"] = GRAVITY_COMMENT; - $datarray["guid"] = $guid; - $datarray["uri"] = $author.":".$guid; - $datarray["parent-uri"] = $parent_item["uri"]; + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; - // The old Diaspora protocol doesn't have a timestamp for comments - $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert(); - $datarray["private"] = $parent_item["private"]; + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); $datarray["owner-name"] = $contact["name"]; $datarray["owner-link"] = $contact["url"]; $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - $datarray["author-name"] = $person["name"]; - $datarray["author-link"] = $person["url"]; - $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); - $datarray["body"] = $body; - $datarray["object"] = json_encode($data); + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; + + $datarray["type"] = "remote-comment"; + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_COMMENT; + $datarray["parent-uri"] = $parent_item["uri"]; + $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; + $datarray["object"] = json_encode($data); + + $datarray["body"] = diaspora2bb($text); self::fetch_guid($datarray); -// $message_id = item_store($datarray); -print_r($datarray); - $datarray["id"] = $message_id; + $message_id = item_store($datarray); + // print_r($datarray); // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -622,6 +650,36 @@ print_r($datarray); return true; } + private function construct_like_body($contact, $parent_item, $guid) { + $bodyverb = t('%1$s likes %2$s\'s %3$s'); + + $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; + $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]"; + $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]"; + + return sprintf($bodyverb, $ulink, $alink, $plink); + } + + private function construct_like_object($importer, $parent_item) { + $objtype = ACTIVITY_OBJ_NOTE; + $link = xmlify('<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'."\n") ; + $parent_body = $parent_item["body"]; + + $obj = <<< EOT + + <object> + <type>$objtype</type> + <local>1</local> + <id>{$parent_item["uri"]}</id> + <link>$link</link> + <title></title> + <content>$parent_body</content> + </object> +EOT; + + return $obj; + } + private function import_like($importer, $sender, $data) { $positive = notags(unxmlify($data->positive)); $guid = notags(unxmlify($data->guid)); @@ -649,7 +707,7 @@ print_r($datarray); logger("Ignoring the author ".$sender); return false; } -/* + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer["uid"]), dbesc($guid) @@ -658,7 +716,7 @@ print_r($datarray); logger("The like already exists: ".$guid); return false; } -*/ + $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) return false; @@ -670,72 +728,37 @@ print_r($datarray); } // Fetch the contact id - if we know this contact - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person["url"])), intval($importer["uid"])); - if ($r) { - $cid = $r[0]["id"]; - $network = $r[0]["network"]; - } else { - $cid = $contact["id"]; - $network = NETWORK_DIASPORA; - } - -// ------------------------------------------------ - $objtype = ACTIVITY_OBJ_NOTE; - $link = xmlify('<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'."\n") ; - $parent_body = $parent_item["body"]; - - $obj = <<< EOT - - <object> - <type>$objtype</type> - <local>1</local> - <id>{$parent_item["uri"]}</id> - <link>$link</link> - <title></title> - <content>$parent_body</content> - </object> -EOT; - $bodyverb = t('%1$s likes %2$s\'s %3$s'); - - $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; - $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]"; - $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]"; - $body = sprintf($bodyverb, $ulink, $alink, $plink); -// ------------------------------------------------ + $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]); $datarray = array(); - $datarray["uri"] = $author.":".$guid; $datarray["uid"] = $importer["uid"]; - $datarray["guid"] = $guid; - $datarray["network"] = $network; - $datarray["contact-id"] = $cid; - $datarray["type"] = "activity"; - $datarray["wall"] = $parent_item["wall"]; - $datarray["gravity"] = GRAVITY_LIKE; - $datarray["parent"] = $parent_item["id"]; - $datarray["parent-uri"] = $parent_item["uri"]; - - $datarray["owner-name"] = $contact["name"]; - $datarray["owner-link"] = $contact["url"]; - $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; $datarray["author-name"] = $person["name"]; $datarray["author-link"] = $person["url"]; $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); - $datarray["body"] = $body; - $datarray["private"] = $parent_item["private"]; - $datarray["verb"] = ACTIVITY_LIKE; - $datarray["object-type"] = $objtype; - $datarray["object"] = $obj; - $datarray["visible"] = 1; - $datarray["unseen"] = 1; - $datarray["last-child"] = 0; + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); -print_r($datarray); -// $message_id = item_store($datarray); + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; + + $datarray["type"] = "activity"; + $datarray["verb"] = ACTIVITY_LIKE; + $datarray["gravity"] = GRAVITY_LIKE; + $datarray["parent-uri"] = $parent_item["uri"]; + + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + $datarray["object"] = self::construct_like_object($importer, $parent_item); + + $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); + + $message_id = item_store($datarray); + //print_r($datarray); // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -751,10 +774,86 @@ print_r($datarray); proc_run("php", "include/notifier.php", "comment-import", $message_id); } - return true; + return $message_id; } private function import_message($importer, $data) { + $guid = notags(unxmlify($data->guid)); + $parent_guid = notags(unxmlify($data->parent_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)); + + $parent_uri = $author.":".$parent_guid; + + $contact = self::get_contact_by_handle($importer["uid"], $author); + if (!$contact) { + logger("cannot find contact: ".$author); + return false; + } + + if(($contact["rel"] == CONTACT_IS_FOLLOWER) || ($contact["blocked"]) || ($contact["readonly"])) { + logger("Ignoring this author."); + return false; + } + + $conversation = null; + + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($conversation_guid) + ); + if(count($c)) + $conversation = $c[0]; + else { + logger("conversation not available."); + return false; + } + + $reply = 0; + + $body = diaspora2bb($text); + $message_id = $author.":".$guid; + + $person = self::get_person_by_handle($author); + if (!$person) { + logger("unable to find author details"); + return false; + } + + $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($message_id), + intval($importer["uid"]) + ); + if(count($r)) { + logger("duplicate message already delivered.", LOGGER_DEBUG); + return false; + } + + q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) + VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + intval($importer["uid"]), + dbesc($guid), + intval($conversation["id"]), + dbesc($person["name"]), + dbesc($person["photo"]), + dbesc($person["url"]), + intval($contact["id"]), + dbesc($conversation["subject"]), + dbesc($body), + 0, + 1, + dbesc($message_id), + dbesc($parent_uri), + dbesc($created_at) + ); + + q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", + dbesc(datetime_convert()), + intval($conversation["id"]) + ); + return true; } @@ -771,14 +870,445 @@ print_r($datarray); } private function import_profile($importer, $data) { + $author = notags(unxmlify($data->author)); + + $contact = self::get_contact_by_handle($importer["uid"], $author); + if (!$contact) + return; + + $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); + $searchable = (unxmlify($data->searchable) == "true"); + $nsfw = (unxmlify($data->nsfw) == "true"); + $tags = unxmlify($data->tag_string); + + $tags = explode("#", $tags); + + $keywords = array(); + foreach ($tags as $tag) { + $tag = trim(strtolower($tag)); + if ($tag != "") + $keywords[] = $tag; + } + + $keywords = implode(", ", $keywords); + + $handle_parts = explode("@", $author); + $nick = $handle_parts[0]; + + if($name === "") + $name = $handle_parts[0]; + + if( preg_match("|^https?://|", $image_url) === 0) + $image_url = "http://".$handle_parts[1].$image_url; + + update_contact_avatar($image_url, $importer["uid"], $contact["id"]); + + // Generic birthday. We don't know the timezone. The year is irrelevant. + + $birthday = str_replace("1000", "1901", $birthday); + + if ($birthday != "") + $birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d"); + + // this is to prevent multiple birthday notifications in a single year + // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year + + if(substr($birthday,5) === substr($contact["bd"],5)) + $birthday = $contact["bd"]; + + $r = q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', `name-date` = '%s', `bd` = '%s', + `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d", + dbesc($name), + dbesc($nick), + dbesc($author), + dbesc(datetime_convert()), + dbesc($birthday), + dbesc($location), + dbesc($about), + dbesc($keywords), + dbesc($gender), + intval($contact["id"]), + intval($importer["uid"]) + ); + + if ($searchable) { + poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", + datetime_convert(), 2, $contact["id"], $importer["uid"]); + } + + $gcontact = array("url" => $contact["url"], "network" => NETWORK_DIASPORA, "generation" => 2, + "photo" => $image_url, "name" => $name, "location" => $location, + "about" => $about, "birthday" => $birthday, "gender" => $gender, + "addr" => $author, "nick" => $nick, "keywords" => $keywords, + "hide" => !$searchable, "nsfw" => $nsfw); + + update_gcontact($gcontact); + return true; } private function import_request($importer, $data) { +print_r($data); +/* + $author = unxmlify($xml->author); + $recipient = unxmlify($xml->recipient); + + if (!$author || !$recipient) + return; + + $contact = self::get_contact_by_handle($importer["uid"],$author); + + if($contact) { + + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { + q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", + intval(CONTACT_IS_FRIEND), + intval($contact["id"]), + intval($importer["uid"]) + ); + } + // send notification + + $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", + intval($importer["uid"]) + ); + + if((count($r)) && (!$r[0]["hide-friends"]) && (!$contact["hidden"]) && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) { + require_once('include/items.php'); + + $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", + intval($importer["uid"]) + ); + + // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array + + if(count($self) && $contact["rel"] == CONTACT_IS_FOLLOWER) { + + $arr = array(); + $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]); + $arr["uid"] = $importer["uid"]; + $arr["contact-id"] = $self[0]["id"]; + $arr["wall"] = 1; + $arr["type"] = 'wall'; + $arr["gravity"] = 0; + $arr["origin"] = 1; + $arr["author-name"] = $arr["owner-name"] = $self[0]["name"]; + $arr["author-link"] = $arr["owner-link"] = $self[0]["url"]; + $arr["author-avatar"] = $arr["owner-avatar"] = $self[0]["thumb"]; + $arr["verb"] = ACTIVITY_FRIEND; + $arr["object-type"] = ACTIVITY_OBJ_PERSON; + + $A = '[url=' . $self[0]["url"] . "]' . $self[0]["name"] . '[/url]'; + $B = '[url=' . $contact["url"] . "]' . $contact["name"] . '[/url]'; + $BPhoto = '[url=' . $contact["url"] . "]' . '[img]' . $contact["thumb"] . '[/img][/url]'; + $arr["body"] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto; + + $arr["object"] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $contact["name"] . '</title>' + . '<id>' . $contact["url"] . '/' . $contact["name"] . '</id>'; + $arr["object"] .= '<link>' . xmlify('<link rel="alternate" type="text/html" href="' . $contact["url"] . '" />' . "\n") +; + $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="' . $contact["thumb"] . '" />' . "\n"); + $arr["object"] .= '</link></object>' . "\n"; + $arr["last-child"] = 1; + + $arr["allow_cid"] = $user[0]["allow_cid"]; + $arr["allow_gid"] = $user[0]["allow_gid"]; + $arr["deny_cid"] = $user[0]["deny_cid"]; + $arr["deny_gid"] = $user[0]["deny_gid"]; + + $i = item_store($arr); + if($i) + proc_run('php',"include/notifier.php","activity","$i"); + + } + + } + + return; + } + + $ret = self::get_person_by_handle($author); + + + if((! count($ret)) || ($ret["network"] != NETWORK_DIASPORA)) { + logger('diaspora_request: Cannot resolve diaspora handle ' . $author . ' for ' . $recipient); + return; + } + + $batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) . '/receive/public'); + + + + $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) + VALUES ( %d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d) ", + intval($importer["uid"]), + dbesc($ret["network"]), + dbesc($ret["addr"]), + datetime_convert(), + dbesc($ret["url"]), + dbesc(normalise_link($ret["url"])), + dbesc($batch), + dbesc($ret["name"]), + dbesc($ret["nick"]), + dbesc($ret["photo"]), + dbesc($ret["pubkey"]), + dbesc($ret["notify"]), + dbesc($ret["poll"]), + 1, + 2 + ); + + // find the contact record we just created + + $contact_record = diaspora_get_contact_by_handle($importer["uid"],$author); + + if(! $contact_record) { + logger('diaspora_request: unable to locate newly created contact record.'); + return; + } + + $g = q("select def_gid from user where uid = %d limit 1", + intval($importer["uid"]) + ); + if($g && intval($g[0]["def_gid"])) { + require_once('include/group.php'); + group_add_member($importer["uid"],'',$contact_record["id"],$g[0]["def_gid"]); + } + + if($importer["page-flags"] == PAGE_NORMAL) { + + $hash = random_string() . (string) time(); // Generate a confirm_key + + $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime` ) + VALUES ( %d, %d, %d, %d, '%s', '%s', '%s' )", + intval($importer["uid"]), + intval($contact_record["id"]), + 0, + 0, + dbesc( t('Sharing notification from Diaspora network')), + dbesc($hash), + dbesc(datetime_convert()) + ); + } + else { + + // automatic friend approval + + require_once('include/Photo.php'); + + update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]); + + // technically they are sharing with us (CONTACT_IS_SHARING), + // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX + // we are going to change the relationship and make them a follower. + + if($importer["page-flags"] == PAGE_FREELOVE) + $new_relation = CONTACT_IS_FRIEND; + else + $new_relation = CONTACT_IS_FOLLOWER; + + $r = q("UPDATE `contact` SET `rel` = %d, + `name-date` = '%s', + `uri-date` = '%s', + `blocked` = 0, + `pending` = 0, + `writable` = 1 + WHERE `id` = %d + ", + intval($new_relation), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($contact_record["id"]) + ); + + $u = q("select * from user where uid = %d limit 1",intval($importer["uid"])); + if($u) + $ret = diaspora_share($u[0],$contact_record); + } +*/ return true; } private function import_reshare($importer, $data) { +/* + $guid = notags(unxmlify($xml->guid)); + $author = notags(unxmlify($xml->author)); + + + if($author != $msg["author"]) { + logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); + return 202; + } + + $contact = diaspora_get_contact_by_handle($importer["uid"],$author); + if(! $contact) + return; + + if(! diaspora_post_allow($importer,$contact, false)) { + logger('diaspora_reshare: Ignoring this author: ' . $author . ' ' . print_r($xml,true)); + return 202; + } + + $message_id = $author . ':' . $guid; + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if(count($r)) { + logger('diaspora_reshare: message exists: ' . $guid); + return; + } + + $orig_author = notags(unxmlify($xml->root_diaspora_id)); + $orig_guid = notags(unxmlify($xml->root_guid)); + $orig_url = $a->get_baseurl()."/display/".$orig_guid; + + $create_original_post = false; + + // Do we already have this item? + $r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT +`deleted` AND `body` != '' LIMIT 1", + dbesc($orig_guid), + dbesc(NETWORK_DIASPORA) + ); + if(count($r)) { + logger('reshared message '.$orig_guid." reshared by ".$guid.' already exists on system.'); + + // Maybe it is already a reshared item? + // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares + require_once('include/api.php'); + if (api_share_as_retweet($r[0])) + $r = array(); + else { + $body = $r[0]["body"]; + $str_tags = $r[0]["tag"]; + $app = $r[0]["app"]; + $orig_created = $r[0]["created"]; + $orig_plink = $r[0]["plink"]; + $orig_uri = $r[0]["uri"]; + $object = $r[0]["object"]; + $objecttype = $r[0]["object-type"]; + } + } + + if (!count($r)) { + $body = ""; + $str_tags = ""; + $app = ""; + + $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); + logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); + $item = diaspora_fetch_message($orig_guid, $server); + + if (!$item) { + $server = 'https://'.substr($author,strpos($author,'@')+1); + logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); + $item = diaspora_fetch_message($orig_guid, $server); + } + if (!$item) { + $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); + logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); + $item = diaspora_fetch_message($orig_guid, $server); + } + if (!$item) { + $server = 'http://'.substr($author,strpos($author,'@')+1); + logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); + $item = diaspora_fetch_message($orig_guid, $server); + } + + if ($item) { + $body = $item["body"]; + $str_tags = $item["tag"]; + $app = $item["app"]; + $orig_created = $item["created"]; + $orig_author = $item["author"]; + $orig_guid = $item["guid"]; + $orig_plink = diaspora_plink($orig_author, $orig_guid); + $orig_uri = $orig_author.':'.$orig_guid; + $create_original_post = ($body != ""); + $object = $item["object"]; + $objecttype = $item["object-type"]; + } + } + + $plink = diaspora_plink($author, $guid); + + $person = find_diaspora_person_by_handle($orig_author); + + $created = unxmlify($xml->created_at); + $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["wall"] = 0; + $datarray["network"] = NETWORK_DIASPORA; + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $message_id; + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created); + $datarray["private"] = $private; + $datarray["parent"] = 0; + $datarray["plink"] = $plink; + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,'thumb')) ? $contact["thumb"] : $contact["photo"]); + $prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]), $orig_guid, $orig_created, $orig_url); + + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = $contact["thumb"]; + $datarray["body"] = $prefix.$body."[/share]"; + + $datarray["object"] = json_encode($xml); + $datarray["object-type"] = $objecttype; + + $datarray["tag"] = $str_tags; + $datarray["app"] = $app; + + // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. (testing) + $datarray["visible"] = ((strlen($body)) ? 1 : 0); + + // Store the original item of a reshare + if ($create_original_post) { + require_once("include/Contact.php"); + + $datarray2 = $datarray; + + $datarray2["uid"] = 0; + $datarray2["contact-id"] = get_contact($person["url"], 0); + $datarray2["guid"] = $orig_guid; + $datarray2["uri"] = $datarray2["parent-uri"] = $orig_uri; + $datarray2["changed"] = $datarray2["created"] = $datarray2["edited"] = $datarray2["commented"] = $datarray2["received"] = datetime_convert('UTC','UTC',$orig_created); + $datarray2["parent"] = 0; + $datarray2["plink"] = $orig_plink; + + $datarray2["author-name"] = $person["name"]; + $datarray2["author-link"] = $person["url"]; + $datarray2["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]); + $datarray2["owner-name"] = $datarray2["author-name"]; + $datarray2["owner-link"] = $datarray2["author-link"]; + $datarray2["owner-avatar"] = $datarray2["author-avatar"]; + $datarray2["body"] = $body; + $datarray2["object"] = $object; + + DiasporaFetchGuid($datarray2); + $message_id = item_store($datarray2); + + logger("Store original item ".$orig_guid." under message id ".$message_id); + } + + DiasporaFetchGuid($datarray); + $message_id = item_store($datarray); +*/ return true; } @@ -786,8 +1316,131 @@ print_r($datarray); return true; } - private function import_status_message($importer, $data) { - return true; + private function import_status_message($importer, $data, $msg, $data2) { + + $raw_message = unxmlify($data->raw_message); + $guid = notags(unxmlify($data->guid)); + $author = notags(unxmlify($data->author)); + $public = notags(unxmlify($data->public)); + $created_at = notags(unxmlify($data->created_at)); + $provider_display_name = notags(unxmlify($data->provider_display_name)); + + foreach ($data->children() AS $name => $entry) + if (count($entry->children())) + if (!in_array($name, array("location", "photo", "poll"))) + die("Kinder: ".$name."\n"); +/* + if ($data->location) { + print_r($location); + foreach ($data->location->children() AS $fieldname => $data) + echo $fieldname." - ".$data."\n"; + die("Location!\n"); + } +*/ +/* + if ($data->photo) { + print_r($data->photo); + foreach ($data->photo->children() AS $fieldname => $data) + echo $fieldname." - ".$data."\n"; + die("Photo!\n"); + } +*/ + + if ($data->poll) { + print_r($data2); + print_r($data); + die("poll!\n"); + } + + + $contact = self::get_contact_by_handle($importer["uid"], $author); + if (!$contact) { + logger("A Contact for handle ".$author." and user ".$importer["uid"]." was not found"); + return false; + } + + if (!self::post_allow($importer, $contact, false)) { + logger("Ignoring this author."); + return false; + } +/* + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if(count($r)) { + logger("message exists: ".$guid); + return false; + } +*/ + $private = (($public == "false") ? 1 : 0); + + $body = diaspora2bb($raw_message); + + $datarray = array(); + + if($data->photo->remote_photo_path AND $data->photo->remote_photo_name) + $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; + else { + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + // Add OEmbed and other information to the body + if (!self::is_redmatrix($contact["url"])) + $body = add_page_info_to_body($body, false, true); + } + + $str_tags = ""; + + $cnt = preg_match_all("/@\[url=(.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + if(strlen($str_tags)) + $str_tags .= ","; + $str_tags .= "@[url=".$mtch[1]."[/url]"; + } + } + $plink = self::plink($author, $guid); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; + + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; + + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; + + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; + + $datarray["object"] = json_encode($data); + + $datarray["body"] = $body; + + $datarray["tag"] = $str_tags; + if ($provider_display_name != "") + $datarray["app"] = $provider_display_name; + + $datarray["plink"] = $plink; + $datarray["private"] = $private; + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); + + // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. + + $datarray["visible"] = ((strlen($body)) ? 1 : 0); + + self::fetch_guid($datarray); + //$message_id = item_store($datarray); + print_r($datarray); + + logger("Stored item with message id ".$message_id, LOGGER_DEBUG); + + return $message_id; } } ?> From 09060c0ed30babe65f659a6a3122b8f148217d16 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Mon, 29 Feb 2016 17:24:34 +0100 Subject: [PATCH 135/273] adding some colors to the Federation Statistics page of the admin panel --- mod/admin.php | 8 +++++++- view/templates/admin_federation.tpl | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mod/admin.php b/mod/admin.php index ecd08bbe00..7a16820c44 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -270,6 +270,12 @@ function admin_page_federation(&$a) { // Add more platforms if you like, when one returns 0 known nodes it is not // displayed on the stats page. $platforms = array('Friendica', 'Diaspora', '%%red%%', 'Hubzilla', 'GNU Social', 'StatusNet'); + $colors = array('Friendica' => '#ffc018', // orange from the logo + 'Diaspora' => '#a1a1a1', // logo is black and white, makes a gray + '%%red%%' => '#c50001', // fire red from the logo + 'Hubzilla' => '#43488a', // blue from the logo + 'GNU Social'=> '#a22430', // dark red from the logo + 'StatusNet' => '#789240'); // the green from the logo (red and blue have already others $counts = array(); $total = 0; @@ -340,7 +346,7 @@ function admin_page_federation(&$a) { // the 3rd array item is needed for the JavaScript graphs as JS does // not like some characters in the names of variables... - $counts[$p]=array($c[0], $v, str_replace(array(' ','%'),'',$p)); + $counts[$p]=array($c[0], $v, str_replace(array(' ','%'),'',$p), $colors[$p]); } // some helpful text diff --git a/view/templates/admin_federation.tpl b/view/templates/admin_federation.tpl index fad87da5b5..ee33df09b2 100644 --- a/view/templates/admin_federation.tpl +++ b/view/templates/admin_federation.tpl @@ -19,7 +19,7 @@ <script> var FedData = [ {{foreach $counts as $c}} - { value: {{$c[0]['total']}}, label: "{{$c[0]['platform']}}", color: "#90EE90", highlight: "#EE90A1", }, + { value: {{$c[0]['total']}}, label: "{{$c[0]['platform']}}", color: '{{$c[3]}}', highlight: "#EE90A1", }, {{/foreach}} ]; var ctx = document.getElementById("FederationChart").getContext("2d"); @@ -40,7 +40,7 @@ var myDoughnutChart = new Chart(ctx).Doughnut(FedData, { animateRotate : false, <script> var {{$c[2]}}data = [ {{foreach $c[1] as $v}} - { value: {{$v['total']}}, label: '{{$v['version']}}', color: "#90EE90", highlight: "#EE90A1",}, + { value: {{$v['total']}}, label: '{{$v['version']}}', color: "{{$c[3]}}", highlight: "#EE90A1",}, {{/foreach}} ]; var ctx = document.getElementById("{{$c[2]}}Chart").getContext("2d"); From 8bcbff30e0984f35bb3d36c22a2edbce437d6369 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 29 Feb 2016 23:54:25 +0100 Subject: [PATCH 136/273] Account deletion should work, status messages works - reshares are half done. --- include/diaspora2.php | 519 ++++++++++++++++++++++++++---------------- 1 file changed, 328 insertions(+), 191 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 578a496c0a..59514ebc76 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -58,7 +58,7 @@ class diaspora { // Use a dummy importer to import the data for the public copy $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); - self::dispatch($importer,$msg); + $item_id = self::dispatch($importer,$msg); // Now distribute it to the followers $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN @@ -74,6 +74,8 @@ class diaspora { } } else logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); + + return $item_id; } public static function dispatch($importer, $msg) { @@ -82,7 +84,7 @@ class diaspora { // This will often be different with relayed messages (for example "like" and "comment") $sender = $msg["author"]; - if (!diaspora::valid_posting($msg, $fields, $data2)) { + if (!diaspora::valid_posting($msg, $fields)) { logger("Invalid posting"); return false; } @@ -90,8 +92,9 @@ class diaspora { $type = $fields->getName(); switch ($type) { - case "account_deletion": // Not implemented - return self::import_account_deletion($importer, $fields); + case "account_deletion": + return true; + //return self::import_account_deletion($importer, $fields); case "comment": return true; @@ -131,7 +134,8 @@ class diaspora { return self::import_retraction($importer, $fields); case "status_message": - return self::import_status_message($importer, $fields, $msg, $data2); + return true; + //return self::import_status_message($importer, $fields, $msg, $data2); default: logger("Unknown message type ".$type); @@ -152,7 +156,7 @@ class diaspora { * * @return bool Is the posting valid? */ - private function valid_posting($msg, &$fields, &$element) { + private function valid_posting($msg, &$fields) { $data = parse_xml_string($msg["message"], false); @@ -554,7 +558,16 @@ class diaspora { } private function import_account_deletion($importer, $data) { - // Not supported by now. We are waiting for sample data + $author = notags(unxmlify($data->author)); + + $contact = self::get_contact_by_handle($importer["uid"], $author); + if (!$contact) { + logger("cannot find contact for sender: ".$sender); + return false; + } + + // We now remove the contact + contact_remove($contact["id"]); return true; } @@ -647,6 +660,167 @@ class diaspora { } private function import_conversation($importer, $data) { +/* + $guid = notags(unxmlify($xml->guid)); + $subject = notags(unxmlify($xml->subject)); + $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); + $participant_handles = notags(unxmlify($xml->participant_handles)); + $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); + + $parent_uri = $diaspora_handle . ':' . $guid; + + $messages = $xml->message; + + if(! count($messages)) { + logger('diaspora_conversation: empty conversation'); + return; + } + + $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); + if(! $contact) { + logger('diaspora_conversation: cannot find contact: ' . $msg['author']); + return; + } + + if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { + logger('diaspora_conversation: Ignoring this author.'); + return 202; + } + + $conversation = null; + + $c = q("select * from conv where uid = %d and guid = '%s' limit 1", + intval($importer['uid']), + dbesc($guid) + ); + if(count($c)) + $conversation = $c[0]; + else { + $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ", + intval($importer['uid']), + dbesc($guid), + dbesc($diaspora_handle), + dbesc(datetime_convert('UTC','UTC',$created_at)), + dbesc(datetime_convert()), + dbesc($subject), + dbesc($participant_handles) + ); + if($r) + $c = q("select * from conv where uid = %d and guid = '%s' limit 1", + intval($importer['uid']), + dbesc($guid) + ); + if(count($c)) + $conversation = $c[0]; + } + if(! $conversation) { + logger('diaspora_conversation: unable to create conversation.'); + return; + } + + foreach($messages as $mesg) { + + $reply = 0; + + $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))); + $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle)); + $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); + if($msg_conversation_guid != $guid) { + logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml); + continue; + } + + $body = diaspora2bb($msg_text); + $message_id = $msg_diaspora_handle . ':' . $msg_guid; + + $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + + $author_signature = base64_decode($msg_author_signature); + + if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) { + $person = $contact; + $key = $msg['key']; + } + else { + $person = find_diaspora_person_by_handle($msg_diaspora_handle); + + if(is_array($person) && x($person,'pubkey')) + $key = $person['pubkey']; + else { + logger('diaspora_conversation: unable to find author details'); + continue; + } + } + + if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { + logger('diaspora_conversation: verification failed.'); + continue; + } + + if($msg_parent_author_signature) { + $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $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('diaspora_conversation: owner verification failed.'); + continue; + } + } + + $r = q("select id from mail where `uri` = '%s' limit 1", + dbesc($message_id) + ); + if(count($r)) { + logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG); + continue; + } + + q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + intval($importer['uid']), + dbesc($msg_guid), + intval($conversation['id']), + dbesc($person['name']), + dbesc($person['photo']), + dbesc($person['url']), + intval($contact['id']), + dbesc($subject), + dbesc($body), + 0, + 0, + dbesc($message_id), + dbesc($parent_uri), + dbesc($msg_created_at) + ); + + q("update conv set updated = '%s' where id = %d", + dbesc(datetime_convert()), + intval($conversation['id']) + ); + + notification(array( + 'type' => NOTIFY_MAIL, + 'notify_flags' => $importer['notify-flags'], + 'language' => $importer['language'], + 'to_name' => $importer['username'], + 'to_email' => $importer['email'], + 'uid' =>$importer['uid'], + 'item' => array('subject' => $subject, 'body' => $body), + 'source_name' => $person['name'], + 'source_link' => $person['url'], + 'source_photo' => $person['thumb'], + 'verb' => ACTIVITY_POST, + 'otype' => 'mail' + )); + } +*/ return true; } @@ -858,14 +1032,17 @@ EOT; } private function import_participation($importer, $data) { + // I'm not sure if we can fully support this message type return true; } private function import_photo($importer, $data) { + // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well return true; } private function import_poll_participation($importer, $data) { + // We don't support polls by now return true; } @@ -1138,185 +1315,156 @@ print_r($data); } private function import_reshare($importer, $data) { + $root_author = notags(unxmlify($data->root_author)); + $root_guid = notags(unxmlify($data->root_guid)); + $guid = notags(unxmlify($data->guid)); + $author = notags(unxmlify($data->author)); + $public = notags(unxmlify($data->public)); + $created_at = notags(unxmlify($data->created_at)); + + $contact = self::get_contact_by_handle($importer["uid"], $author); + if (!$contact) + return false; + + if (!self::post_allow($importer, $contact, false)) { + logger("Ignoring this author: ".$author." ".print_r($data,true)); + return false; + } /* - $guid = notags(unxmlify($xml->guid)); - $author = notags(unxmlify($xml->author)); - - - if($author != $msg["author"]) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } - - $contact = diaspora_get_contact_by_handle($importer["uid"],$author); - if(! $contact) - return; - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_reshare: Ignoring this author: ' . $author . ' ' . print_r($xml,true)); - return 202; - } - - $message_id = $author . ':' . $guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_reshare: message exists: ' . $guid); - return; - } - - $orig_author = notags(unxmlify($xml->root_diaspora_id)); - $orig_guid = notags(unxmlify($xml->root_guid)); - $orig_url = $a->get_baseurl()."/display/".$orig_guid; - - $create_original_post = false; - - // Do we already have this item? - $r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT -`deleted` AND `body` != '' LIMIT 1", - dbesc($orig_guid), - dbesc(NETWORK_DIASPORA) - ); - if(count($r)) { - logger('reshared message '.$orig_guid." reshared by ".$guid.' already exists on system.'); - - // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - require_once('include/api.php'); - if (api_share_as_retweet($r[0])) - $r = array(); - else { - $body = $r[0]["body"]; - $str_tags = $r[0]["tag"]; - $app = $r[0]["app"]; - $orig_created = $r[0]["created"]; - $orig_plink = $r[0]["plink"]; - $orig_uri = $r[0]["uri"]; - $object = $r[0]["object"]; - $objecttype = $r[0]["object-type"]; + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if(count($r)) { + logger("message exists: ".$guid); + return; } - } +*/ + $orig_author = $root_author; + $orig_guid = $root_guid; + $orig_url = App::get_baseurl()."/display/".$guid; - if (!count($r)) { - $body = ""; - $str_tags = ""; - $app = ""; + $create_original_post = false; - $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = diaspora_fetch_message($orig_guid, $server); + // Do we already have this item? + $r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", + dbesc($orig_guid), + dbesc(NETWORK_DIASPORA) + ); + if(count($r)) { + logger("reshared message ".$orig_guid." reshared by ".$guid." already exists on system."); - if (!$item) { - $server = 'https://'.substr($author,strpos($author,'@')+1); - logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($author,strpos($author,'@')+1); - logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); + // Maybe it is already a reshared item? + // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares + require_once('include/api.php'); + if (api_share_as_retweet($r[0])) + $r = array(); + else { + $body = $r[0]["body"]; + $str_tags = $r[0]["tag"]; + $app = $r[0]["app"]; + $orig_created = $r[0]["created"]; + $orig_plink = $r[0]["plink"]; + $orig_uri = $r[0]["uri"]; + $object = $r[0]["object"]; + $objecttype = $r[0]["object-type"]; + } } - if ($item) { - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $orig_created = $item["created"]; - $orig_author = $item["author"]; - $orig_guid = $item["guid"]; - $orig_plink = diaspora_plink($orig_author, $orig_guid); - $orig_uri = $orig_author.':'.$orig_guid; - $create_original_post = ($body != ""); - $object = $item["object"]; - $objecttype = $item["object-type"]; +/* @todo + if (!count($r)) { + $body = ""; + $str_tags = ""; + $app = ""; + + $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); + logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); + $item = self::fetch_message($orig_guid, $server); + + if (!$item) { + $server = 'https://'.substr($author,strpos($author,'@')+1); + logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); + $item = diaspora_fetch_message($orig_guid, $server); + } + if (!$item) { + $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); + logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); + $item = diaspora_fetch_message($orig_guid, $server); + } + if (!$item) { + $server = 'http://'.substr($author,strpos($author,'@')+1); + logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); + $item = diaspora_fetch_message($orig_guid, $server); + } + + if ($item) { + $body = $item["body"]; + $str_tags = $item["tag"]; + $app = $item["app"]; + $orig_created = $item["created"]; + $orig_author = $item["author"]; + $orig_guid = $item["guid"]; + $orig_plink = diaspora_plink($orig_author, $orig_guid); + $orig_uri = $orig_author.":".$orig_guid; + $create_original_post = ($body != ""); + $object = $item["object"]; + $objecttype = $item["object-type"]; + } } - } +*/ + $plink = self::plink($author, $guid); - $plink = diaspora_plink($author, $guid); + $person = self::get_person_by_handle($orig_author); - $person = find_diaspora_person_by_handle($orig_author); + $private = (($public == "false") ? 1 : 0); - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); + $datarray = array(); - $datarray = array(); - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $contact["id"]; - $datarray["wall"] = 0; - $datarray["network"] = NETWORK_DIASPORA; - $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = $message_id; - $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created); - $datarray["private"] = $private; - $datarray["parent"] = 0; - $datarray["plink"] = $plink; - $datarray["owner-name"] = $contact["name"]; - $datarray["owner-link"] = $contact["url"]; - $datarray["owner-avatar"] = ((x($contact,'thumb')) ? $contact["thumb"] : $contact["photo"]); - $prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]), $orig_guid, $orig_created, $orig_url); + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; $datarray["author-name"] = $contact["name"]; $datarray["author-link"] = $contact["url"]; - $datarray["author-avatar"] = $contact["thumb"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; + + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; + + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; + + $datarray["object"] = json_encode($data); + + $prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]), + $orig_guid, $orig_created, $orig_url); $datarray["body"] = $prefix.$body."[/share]"; - $datarray["object"] = json_encode($xml); - $datarray["object-type"] = $objecttype; + $datarray["tag"] = $str_tags; + $datarray["app"] = $app; - $datarray["tag"] = $str_tags; - $datarray["app"] = $app; + $datarray["plink"] = $plink; + $datarray["private"] = $private; + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. (testing) - $datarray["visible"] = ((strlen($body)) ? 1 : 0); + $datarray["object-type"] = $objecttype; - // Store the original item of a reshare - if ($create_original_post) { - require_once("include/Contact.php"); + self::fetch_guid($datarray); + //$message_id = item_store($datarray); + print_r($datarray); - $datarray2 = $datarray; - - $datarray2["uid"] = 0; - $datarray2["contact-id"] = get_contact($person["url"], 0); - $datarray2["guid"] = $orig_guid; - $datarray2["uri"] = $datarray2["parent-uri"] = $orig_uri; - $datarray2["changed"] = $datarray2["created"] = $datarray2["edited"] = $datarray2["commented"] = $datarray2["received"] = datetime_convert('UTC','UTC',$orig_created); - $datarray2["parent"] = 0; - $datarray2["plink"] = $orig_plink; - - $datarray2["author-name"] = $person["name"]; - $datarray2["author-link"] = $person["url"]; - $datarray2["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]); - $datarray2["owner-name"] = $datarray2["author-name"]; - $datarray2["owner-link"] = $datarray2["author-link"]; - $datarray2["owner-avatar"] = $datarray2["author-avatar"]; - $datarray2["body"] = $body; - $datarray2["object"] = $object; - - DiasporaFetchGuid($datarray2); - $message_id = item_store($datarray2); - - logger("Store original item ".$orig_guid." under message id ".$message_id); - } - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); -*/ - return true; + return $message_id; } private function import_retraction($importer, $data) { return true; } - private function import_status_message($importer, $data, $msg, $data2) { + private function import_status_message($importer, $data) { $raw_message = unxmlify($data->raw_message); $guid = notags(unxmlify($data->guid)); @@ -1325,34 +1473,6 @@ print_r($data); $created_at = notags(unxmlify($data->created_at)); $provider_display_name = notags(unxmlify($data->provider_display_name)); - foreach ($data->children() AS $name => $entry) - if (count($entry->children())) - if (!in_array($name, array("location", "photo", "poll"))) - die("Kinder: ".$name."\n"); -/* - if ($data->location) { - print_r($location); - foreach ($data->location->children() AS $fieldname => $data) - echo $fieldname." - ".$data."\n"; - die("Location!\n"); - } -*/ -/* - if ($data->photo) { - print_r($data->photo); - foreach ($data->photo->children() AS $fieldname => $data) - echo $fieldname." - ".$data."\n"; - die("Photo!\n"); - } -*/ - - if ($data->poll) { - print_r($data2); - print_r($data); - die("poll!\n"); - } - - $contact = self::get_contact_by_handle($importer["uid"], $author); if (!$contact) { logger("A Contact for handle ".$author." and user ".$importer["uid"]." was not found"); @@ -1363,7 +1483,7 @@ print_r($data); logger("Ignoring this author."); return false; } -/* + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($importer["uid"]), dbesc($guid) @@ -1372,11 +1492,26 @@ print_r($data); logger("message exists: ".$guid); return false; } -*/ + + /// @todo enable support for polls + // if ($data->poll) { + // print_r($data->poll); + // die("poll!\n"); + // } + + $address = array(); + if ($data->location) + foreach ($data->location->children() AS $fieldname => $data) + $address[$fieldname] = notags(unxmlify($data)); + $private = (($public == "false") ? 1 : 0); $body = diaspora2bb($raw_message); + if ($data->photo) + for ($i = 0; $i < count($data->photo); $i++) + $body = "[img]".$data->photo[$i]->remote_photo_path.$data->photo[$i]->remote_photo_name."[/img]\n".$body; + $datarray = array(); if($data->photo->remote_photo_path AND $data->photo->remote_photo_name) @@ -1430,9 +1565,11 @@ print_r($data); $datarray["private"] = $private; $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. + if (isset($address["address"])) + $datarray["location"] = $address["address"]; - $datarray["visible"] = ((strlen($body)) ? 1 : 0); + if (isset($address["lat"]) AND isset($address["lng"])) + $datarray["coord"] = $address["lat"]." ".$address["lng"]; self::fetch_guid($datarray); //$message_id = item_store($datarray); From cb900c5742628ad437b7fc2d06d10e9999a06aaf Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 1 Mar 2016 07:57:26 +0100 Subject: [PATCH 137/273] Reshares could work now, code is cleaned --- include/diaspora2.php | 356 +++++++++++++++++------------------------- 1 file changed, 147 insertions(+), 209 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 59514ebc76..a1297c5193 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -4,11 +4,14 @@ * @brief The implementation of the diaspora protocol */ +require_once("include/items.php"); require_once("include/bb2diaspora.php"); require_once("include/Scrape.php"); require_once("include/Contact.php"); require_once("include/Photo.php"); require_once("include/socgraph.php"); +require_once("include/group.php"); +require_once("include/api.php"); class xml { function from_array($array, &$xml) { @@ -380,6 +383,64 @@ class diaspora { return false; } + private function post_allow($importer, $contact, $is_comment = false) { + + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + // Normally this should have handled by getting a request - but this could get lost + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { + q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", + intval(CONTACT_IS_FRIEND), + intval($contact["id"]), + intval($importer["uid"]) + ); + $contact["rel"] = CONTACT_IS_FRIEND; + logger("defining user ".$contact["nick"]." as friend"); + } + + if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"])) + return false; + if($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) + return true; + + return false; + } + + private function get_allowed_contact_by_handle($importer, $handle, $is_comment = false) { + $contact = self::get_contact_by_handle($importer["uid"], $handle); + if (!$contact) { + logger("A Contact for handle ".$handle." and user ".$importer["uid"]." was not found"); + return false; + } + + if (!self::post_allow($importer, $contact, false)) { + logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]); + return false; + } + return $contact; + } + + private function message_exists($uid, $guid) { + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), + dbesc($guid) + ); + + if(count($r)) { + logger("message ".$guid." already exists for user ".$uid); + return false; + } + + return true; + } + private function fetch_guid($item) { preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", function ($match) use ($item){ @@ -388,8 +449,6 @@ class diaspora { } private function fetch_guid_sub($match, $item) { - $a = get_app(); - if (!self::store_by_guid($match[1], $item["author-link"])) self::store_by_guid($match[1], $item["owner-link"]); } @@ -453,36 +512,6 @@ class diaspora { return $msg; } - private function post_allow($importer, $contact, $is_comment = false) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - // Normally this should have handled by getting a request - but this could get lost - if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { - q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", - intval(CONTACT_IS_FRIEND), - intval($contact["id"]), - intval($importer["uid"]) - ); - $contact["rel"] = CONTACT_IS_FRIEND; - logger("defining user ".$contact["nick"]." as friend"); - } - - if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"])) - return false; - if($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) - return true; - - return false; - } - private function fetch_parent_item($uid, $guid, $author, $contact) { $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, `author-name`, `author-link`, `author-avatar`, @@ -562,7 +591,7 @@ class diaspora { $contact = self::get_contact_by_handle($importer["uid"], $author); if (!$contact) { - logger("cannot find contact for sender: ".$sender); + logger("cannot find contact for author: ".$author); return false; } @@ -577,25 +606,12 @@ class diaspora { $text = unxmlify($data->text); $author = notags(unxmlify($data->author)); - $contact = self::get_contact_by_handle($importer["uid"], $sender); - if (!$contact) { - logger("cannot find contact for sender: ".$sender); + $contact = self::get_allowed_contact_by_handle($importer, $sender, true); + if (!$contact) return false; - } - if (!self::post_allow($importer,$contact, true)) { - logger("Ignoring the author ".$sender); + if (self::message_exists($importer["uid"], $guid)) return false; - } - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - if(count($r)) { - logger("The comment already exists: ".$guid); - return false; - } $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) @@ -676,16 +692,9 @@ class diaspora { return; } - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_conversation: cannot find contact: ' . $msg['author']); - return; - } - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { - logger('diaspora_conversation: Ignoring this author.'); - return 202; - } + $contact = self::get_allowed_contact_by_handle($importer, $sender, true) + if (!$contact) + return false; $conversation = null; @@ -871,25 +880,12 @@ EOT; return false; } - $contact = self::get_contact_by_handle($importer["uid"], $sender); - if (!$contact) { - logger("cannot find contact for sender: ".$sender); + $contact = self::get_allowed_contact_by_handle($importer, $sender, true); + if (!$contact) return false; - } - if (!self::post_allow($importer,$contact, true)) { - logger("Ignoring the author ".$sender); + if (self::message_exists($importer["uid"], $guid)) return false; - } - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - if(count($r)) { - logger("The like already exists: ".$guid); - return false; - } $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) @@ -961,16 +957,9 @@ EOT; $parent_uri = $author.":".$parent_guid; - $contact = self::get_contact_by_handle($importer["uid"], $author); - if (!$contact) { - logger("cannot find contact: ".$author); + $contact = self::get_allowed_contact_by_handle($importer, $author, true); + if (!$contact) return false; - } - - if(($contact["rel"] == CONTACT_IS_FOLLOWER) || ($contact["blocked"]) || ($contact["readonly"])) { - logger("Ignoring this author."); - return false; - } $conversation = null; @@ -1159,7 +1148,6 @@ print_r($data); ); if((count($r)) && (!$r[0]["hide-friends"]) && (!$contact["hidden"]) && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) { - require_once('include/items.php'); $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", intval($importer["uid"]) @@ -1170,7 +1158,7 @@ print_r($data); if(count($self) && $contact["rel"] == CONTACT_IS_FOLLOWER) { $arr = array(); - $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]); + $arr["uri"] = $arr["parent-uri"] = item_new_uri(App::get_hostname(), $importer["uid"]); $arr["uid"] = $importer["uid"]; $arr["contact-id"] = $self[0]["id"]; $arr["wall"] = 1; @@ -1256,7 +1244,6 @@ print_r($data); intval($importer["uid"]) ); if($g && intval($g[0]["def_gid"])) { - require_once('include/group.php'); group_add_member($importer["uid"],'',$contact_record["id"],$g[0]["def_gid"]); } @@ -1279,8 +1266,6 @@ print_r($data); // automatic friend approval - require_once('include/Photo.php'); - update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]); // technically they are sharing with us (CONTACT_IS_SHARING), @@ -1314,6 +1299,60 @@ print_r($data); return true; } + private function get_original_item($guid, $orig_author, $author) { + + // Do we already have this item? + $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, + `author-name`, `author-link`, `author-avatar` + FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", + dbesc($guid)); + + if(count($r)) { + logger("reshared message ".$guid." already exists on system."); + + // Maybe it is already a reshared item? + // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares + if (api_share_as_retweet($r[0])) + $r = array(); + else + return $r[0]; + } + + if (!count($r)) { + $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); + logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); + $item_id = self::store_by_guid($guid, $server); + + if (!$item_id) { + $server = 'https://'.substr($author,strpos($author,'@')+1); + logger("2nd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item = self::store_by_guid($guid, $server); + } + if (!$item_id) { + $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); + logger("3rd try: reshared message ".$guid." will be fetched from original server: ".$server); + $item = self::store_by_guid($guid, $server); + } + if (!$item_id) { + $server = 'http://'.substr($author,strpos($author,'@')+1); + logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item = self::store_by_guid($guid, $server); + } + + if ($item_id) { + $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, + `author-name`, `author-link`, `author-avatar` + FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", + intval($item_id)); + + if ($r) + return $r[0]; + + } + } + return false; + } + private function import_reshare($importer, $data) { $root_author = notags(unxmlify($data->root_author)); $root_guid = notags(unxmlify($data->root_guid)); @@ -1322,101 +1361,16 @@ print_r($data); $public = notags(unxmlify($data->public)); $created_at = notags(unxmlify($data->created_at)); - $contact = self::get_contact_by_handle($importer["uid"], $author); + $contact = self::get_allowed_contact_by_handle($importer, $author, false); if (!$contact) return false; - if (!self::post_allow($importer, $contact, false)) { - logger("Ignoring this author: ".$author." ".print_r($data,true)); +// if (self::message_exists($importer["uid"], $guid)) +// return false; + + $original_item = self::get_original_item($root_guid, $root_author, $author); + if (!$original_item) return false; - } -/* - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - if(count($r)) { - logger("message exists: ".$guid); - return; - } -*/ - $orig_author = $root_author; - $orig_guid = $root_guid; - $orig_url = App::get_baseurl()."/display/".$guid; - - $create_original_post = false; - - // Do we already have this item? - $r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", - dbesc($orig_guid), - dbesc(NETWORK_DIASPORA) - ); - if(count($r)) { - logger("reshared message ".$orig_guid." reshared by ".$guid." already exists on system."); - - // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - require_once('include/api.php'); - if (api_share_as_retweet($r[0])) - $r = array(); - else { - $body = $r[0]["body"]; - $str_tags = $r[0]["tag"]; - $app = $r[0]["app"]; - $orig_created = $r[0]["created"]; - $orig_plink = $r[0]["plink"]; - $orig_uri = $r[0]["uri"]; - $object = $r[0]["object"]; - $objecttype = $r[0]["object-type"]; - } - } - -/* @todo - if (!count($r)) { - $body = ""; - $str_tags = ""; - $app = ""; - - $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = self::fetch_message($orig_guid, $server); - - if (!$item) { - $server = 'https://'.substr($author,strpos($author,'@')+1); - logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($author,strpos($author,'@')+1); - logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - - if ($item) { - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $orig_created = $item["created"]; - $orig_author = $item["author"]; - $orig_guid = $item["guid"]; - $orig_plink = diaspora_plink($orig_author, $orig_guid); - $orig_uri = $orig_author.":".$orig_guid; - $create_original_post = ($body != ""); - $object = $item["object"]; - $objecttype = $item["object-type"]; - } - } -*/ - $plink = self::plink($author, $guid); - - $person = self::get_person_by_handle($orig_author); - - $private = (($public == "false") ? 1 : 0); $datarray = array(); @@ -1440,18 +1394,18 @@ print_r($data); $datarray["object"] = json_encode($data); - $prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]), - $orig_guid, $orig_created, $orig_url); - $datarray["body"] = $prefix.$body."[/share]"; + $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], + $original_item["guid"], $original_item["created"], $original_item["uri"]); + $datarray["body"] = $prefix.$original_item["body"]."[/share]"; - $datarray["tag"] = $str_tags; - $datarray["app"] = $app; + $datarray["tag"] = $original_item["tag"]; + $datarray["app"] = $original_item["app"]; - $datarray["plink"] = $plink; - $datarray["private"] = $private; + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - $datarray["object-type"] = $objecttype; + $datarray["object-type"] = $original_item["object-type"]; self::fetch_guid($datarray); //$message_id = item_store($datarray); @@ -1473,25 +1427,12 @@ print_r($data); $created_at = notags(unxmlify($data->created_at)); $provider_display_name = notags(unxmlify($data->provider_display_name)); - $contact = self::get_contact_by_handle($importer["uid"], $author); - if (!$contact) { - logger("A Contact for handle ".$author." and user ".$importer["uid"]." was not found"); + $contact = self::get_allowed_contact_by_handle($importer, $author, false); + if (!$contact) return false; - } - if (!self::post_allow($importer, $contact, false)) { - logger("Ignoring this author."); + if (self::message_exists($importer["uid"], $guid)) return false; - } - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - if(count($r)) { - logger("message exists: ".$guid); - return false; - } /// @todo enable support for polls // if ($data->poll) { @@ -1504,8 +1445,6 @@ print_r($data); foreach ($data->location->children() AS $fieldname => $data) $address[$fieldname] = notags(unxmlify($data)); - $private = (($public == "false") ? 1 : 0); - $body = diaspora2bb($raw_message); if ($data->photo) @@ -1533,7 +1472,6 @@ print_r($data); $str_tags .= "@[url=".$mtch[1]."[/url]"; } } - $plink = self::plink($author, $guid); $datarray["uid"] = $importer["uid"]; $datarray["contact-id"] = $contact["id"]; @@ -1561,8 +1499,8 @@ print_r($data); if ($provider_display_name != "") $datarray["app"] = $provider_display_name; - $datarray["plink"] = $plink; - $datarray["private"] = $private; + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); if (isset($address["address"])) From 04a7d6384ef5ae137019286c3819fef1d5afcf6b Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Tue, 1 Mar 2016 14:27:38 +0100 Subject: [PATCH 138/273] No need to set $a->theme_info = array() everywhere if you can define it in App itself. Signed-off-by: Roland Haeder <roland@mxchange.org> --- boot.php | 5 +++-- view/theme/decaf-mobile/theme.php | 1 - view/theme/duepuntozero/theme.php | 1 - view/theme/facepark/theme.php | 1 - view/theme/frost-mobile/theme.php | 1 - view/theme/frost/theme.php | 1 - view/theme/quattro/theme.php | 2 -- view/theme/smoothly/theme.php | 1 - view/theme/vier/theme.php | 2 -- 9 files changed, 3 insertions(+), 12 deletions(-) diff --git a/boot.php b/boot.php index d82669f231..4b2e83f18a 100644 --- a/boot.php +++ b/boot.php @@ -465,11 +465,12 @@ class App { public $plugins; public $apps = array(); public $identities; - public $is_mobile; - public $is_tablet; + public $is_mobile = false; + public $is_tablet = false; public $is_friendica_app; public $performance = array(); public $callstack = array(); + public $theme_info = array(); public $nav_sel; diff --git a/view/theme/decaf-mobile/theme.php b/view/theme/decaf-mobile/theme.php index 1a32fb724d..9b1c1daa61 100644 --- a/view/theme/decaf-mobile/theme.php +++ b/view/theme/decaf-mobile/theme.php @@ -10,7 +10,6 @@ */ function decaf_mobile_init(&$a) { - $a->theme_info = array(); $a->sourcename = 'Friendica mobile web'; $a->videowidth = 250; $a->videoheight = 200; diff --git a/view/theme/duepuntozero/theme.php b/view/theme/duepuntozero/theme.php index ba3f25d3e2..50d57f91e5 100644 --- a/view/theme/duepuntozero/theme.php +++ b/view/theme/duepuntozero/theme.php @@ -2,7 +2,6 @@ function duepuntozero_init(&$a) { -$a->theme_info = array(); set_template_engine($a, 'smarty3'); $colorset = get_pconfig( local_user(), 'duepuntozero','colorset'); diff --git a/view/theme/facepark/theme.php b/view/theme/facepark/theme.php index e7203c6641..e6255f118a 100644 --- a/view/theme/facepark/theme.php +++ b/view/theme/facepark/theme.php @@ -1,7 +1,6 @@ <?php function facepark_init(&$a) { -$a->theme_info = array(); set_template_engine($a, 'smarty3'); $a->page['htmlhead'] .= <<< EOT diff --git a/view/theme/frost-mobile/theme.php b/view/theme/frost-mobile/theme.php index beec924934..29a990f7b8 100644 --- a/view/theme/frost-mobile/theme.php +++ b/view/theme/frost-mobile/theme.php @@ -10,7 +10,6 @@ */ function frost_mobile_init(&$a) { - $a->theme_info = array(); $a->sourcename = 'Friendica mobile web'; $a->videowidth = 250; $a->videoheight = 200; diff --git a/view/theme/frost/theme.php b/view/theme/frost/theme.php index 868a840dee..1093a04729 100644 --- a/view/theme/frost/theme.php +++ b/view/theme/frost/theme.php @@ -10,7 +10,6 @@ */ function frost_init(&$a) { - $a->theme_info = array(); $a->videowidth = 400; $a->videoheight = 330; $a->theme_thread_allow = false; diff --git a/view/theme/quattro/theme.php b/view/theme/quattro/theme.php index a1cd29ee72..0b67c6b49a 100644 --- a/view/theme/quattro/theme.php +++ b/view/theme/quattro/theme.php @@ -8,8 +8,6 @@ */ function quattro_init(&$a) { - $a->theme_info = array(); - $a->page['htmlhead'] .= '<script src="'.$a->get_baseurl().'/view/theme/quattro/tinycon.min.js"></script>'; $a->page['htmlhead'] .= '<script src="'.$a->get_baseurl().'/view/theme/quattro/js/quattro.js"></script>';; } diff --git a/view/theme/smoothly/theme.php b/view/theme/smoothly/theme.php index d3ebbc1d15..0dae3a6e47 100644 --- a/view/theme/smoothly/theme.php +++ b/view/theme/smoothly/theme.php @@ -11,7 +11,6 @@ */ function smoothly_init(&$a) { - $a->theme_info = array(); set_template_engine($a, 'smarty3'); $cssFile = null; diff --git a/view/theme/vier/theme.php b/view/theme/vier/theme.php index 7f6ead079f..925ac76a1f 100644 --- a/view/theme/vier/theme.php +++ b/view/theme/vier/theme.php @@ -19,8 +19,6 @@ function vier_init(&$a) { set_template_engine($a, 'smarty3'); - $a->theme_info = array(); - if ($a->argv[0].$a->argv[1] === "profile".$a->user['nickname'] or $a->argv[0] === "network" && local_user()) { vier_community_info(); From f10d2afca0edfef804aa95affe77bba99a603b3b Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Tue, 1 Mar 2016 14:32:19 +0100 Subject: [PATCH 139/273] Prevent some E_NOTICE in boot.php Signed-off-by: Roland Haeder <roland@mxchange.org> --- boot.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/boot.php b/boot.php index d82669f231..44fbcda735 100644 --- a/boot.php +++ b/boot.php @@ -1046,11 +1046,21 @@ class App { function save_timestamp($stamp, $value) { $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; } From 04eacb64703c8d2a590c64da7ed5fae80ac26769 Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Tue, 1 Mar 2016 14:36:23 +0100 Subject: [PATCH 140/273] Prevent some E_NOTICE in identity.php Signed-off-by: Roland Haeder <roland@mxchange.org> --- include/identity.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/identity.php b/include/identity.php index aba69bae49..888a09ee6f 100644 --- a/include/identity.php +++ b/include/identity.php @@ -237,6 +237,7 @@ function profile_sidebar($profile, $block = 0) { if ($connect AND ($profile['network'] != NETWORK_DFRN) AND !isset($profile['remoteconnect'])) $connect = false; + $remoteconnect = NULL; if (isset($profile['remoteconnect'])) $remoteconnect = $profile['remoteconnect']; @@ -292,9 +293,9 @@ function profile_sidebar($profile, $block = 0) { // check if profile is a forum if((intval($profile['page-flags']) == PAGE_COMMUNITY) || (intval($profile['page-flags']) == PAGE_PRVGROUP) - || (intval($profile['forum'])) - || (intval($profile['prv'])) - || (intval($profile['community']))) + || (isset($profile['forum']) && intval($profile['forum'])) + || (isset($profile['prv']) && intval($profile['prv'])) + || (isset($profile['community']) && intval($profile['community']))) $account_type = t('Forum'); else $account_type = ""; From 76b042ddee262aa487839b1d47109a72444cfab2 Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Tue, 1 Mar 2016 14:42:55 +0100 Subject: [PATCH 141/273] More logging in case of errors + logged network type + added TODO (for ugly E_NOTICE). Signed-off-by: Roland Haeder <roland@mxchange.org> --- mod/dfrn_request.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mod/dfrn_request.php b/mod/dfrn_request.php index 2741ad59b4..82886a6efa 100644 --- a/mod/dfrn_request.php +++ b/mod/dfrn_request.php @@ -42,8 +42,10 @@ function dfrn_request_init(&$a) { if(! function_exists('dfrn_request_post')) { function dfrn_request_post(&$a) { - if(($a->argc != 2) || (! count($a->profile))) + if(($a->argc != 2) || (! count($a->profile))) { + logger('Wrong count of argc or profiles: argc=' . $a->argc . ',profile()=' . count($a->profile)); return; + } if(x($_POST, 'cancel')) { @@ -461,7 +463,7 @@ function dfrn_request_post(&$a) { $network = NETWORK_DFRN; } - logger('dfrn_request: url: ' . $url); + logger('dfrn_request: url: ' . $url . ',network=' . $network, LOGGER_DEBUG); if($network === NETWORK_DFRN) { $ret = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `self` = 0 LIMIT 1", @@ -825,6 +827,7 @@ function dfrn_request_content(&$a) { else $tpl = get_markup_template('auto_request.tpl'); + // TODO This .= triggers an E_NOTICE, really needed? $page_desc .= t("Please enter your 'Identity Address' from one of the following supported communications networks:"); // see if we are allowed to have NETWORK_MAIL2 contacts @@ -850,6 +853,7 @@ function dfrn_request_content(&$a) { get_server() ); + // TODO This .= triggers an E_NOTICE, really needed? $o .= replace_macros($tpl,array( '$header' => t('Friend/Connection Request'), '$desc' => t('Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca'), From 8616f0cc3526b0f0ca3d16800984e118c5d56d94 Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Tue, 1 Mar 2016 14:49:07 +0100 Subject: [PATCH 142/273] Don't miss to add exit() after header('Location: bla') as header() does *NOT* exit the script quickly enough. Always use an explicit exit() or you get real trouble. Signed-off-by: Roland Haeder <roland@mxchange.org> --- index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.php b/index.php index e364389b2c..625c2d82dc 100644 --- a/index.php +++ b/index.php @@ -72,7 +72,8 @@ if(!$install) { (intval(get_config('system','ssl_policy')) == SSL_POLICY_FULL) AND (substr($a->get_baseurl(), 0, 8) == "https://")) { header("HTTP/1.1 302 Moved Temporarily"); - header("location: ".$a->get_baseurl()."/".$a->query_string); + header("Location: ".$a->get_baseurl()."/".$a->query_string); + exit(); } require_once("include/session.php"); From 08abdfda684c6b9215109da6f999351f81c00ac3 Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Tue, 1 Mar 2016 14:49:56 +0100 Subject: [PATCH 143/273] <form> and <html> or <head> are not self-closing tags. Signed-off-by: Roland Haeder <roland@mxchange.org> --- view/templates/dfrn_request.tpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/view/templates/dfrn_request.tpl b/view/templates/dfrn_request.tpl index 3b96d3eefd..5225bd60b2 100644 --- a/view/templates/dfrn_request.tpl +++ b/view/templates/dfrn_request.tpl @@ -18,13 +18,13 @@ {{/if}} {{if $request}} -<form action="{{$request}}" method="post" /> +<form action="{{$request}}" method="post"> {{else}} -<form action="dfrn_request/{{$nickname}}" method="post" /> +<form action="dfrn_request/{{$nickname}}" method="post"> {{/if}} {{if $photo}} -<img src="{{$photo}}" alt="" id="dfrn-request-photo"> +<img src="{{$photo}}" alt="" id="dfrn-request-photo" /> {{/if}} {{if $url}}<dl><dt>{{$url_label}}</dt><dd><a target="blank" href="{{$zrl}}">{{$url}}</a></dd></dl>{{/if}} From 7d5166c9fefd03ec63807813e831017e6ad25382 Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Tue, 1 Mar 2016 18:28:06 +0100 Subject: [PATCH 144/273] Closed TODO: no .= needed here. #2392 Signed-off-by: Roland Haeder <roland@mxchange.org> --- mod/dfrn_request.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mod/dfrn_request.php b/mod/dfrn_request.php index 82886a6efa..5455996069 100644 --- a/mod/dfrn_request.php +++ b/mod/dfrn_request.php @@ -827,8 +827,7 @@ function dfrn_request_content(&$a) { else $tpl = get_markup_template('auto_request.tpl'); - // TODO This .= triggers an E_NOTICE, really needed? - $page_desc .= t("Please enter your 'Identity Address' from one of the following supported communications networks:"); + $page_desc = t("Please enter your 'Identity Address' from one of the following supported communications networks:"); // see if we are allowed to have NETWORK_MAIL2 contacts @@ -853,8 +852,7 @@ function dfrn_request_content(&$a) { get_server() ); - // TODO This .= triggers an E_NOTICE, really needed? - $o .= replace_macros($tpl,array( + $o = replace_macros($tpl,array( '$header' => t('Friend/Connection Request'), '$desc' => t('Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, testuser@identi.ca'), '$pls_answer' => t('Please answer the following:'), From 009cadf63beada12c3e53f5d60a5542321a3f470 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 1 Mar 2016 19:10:35 +0100 Subject: [PATCH 145/273] Just another commit message :-) --- include/diaspora.php | 6 +++--- include/diaspora2.php | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 4ec7489ca4..9db9e6056e 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -146,9 +146,9 @@ function diaspora_dispatch($importer,$msg,$attempt=1) { $ret = diaspora_participation($importer,$xmlbase->participation); } elseif($xmlbase->poll_participation) { - $tempfile = tempnam(get_temppath(), "diaspora-poll_participation"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_participation($importer,$xmlbase->participation); + //$tempfile = tempnam(get_temppath(), "diaspora-poll_participation"); + //file_put_contents($tempfile, json_encode($data)); + $ret = diaspora_participation($importer,$xmlbase->poll_participation); } else { $tempfile = tempnam(get_temppath(), "diaspora-unknown"); diff --git a/include/diaspora2.php b/include/diaspora2.php index a1297c5193..e8ed80ee80 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -131,7 +131,8 @@ class diaspora { return self::import_request($importer, $fields); case "reshare": - return self::import_reshare($importer, $fields); + return true; + //return self::import_reshare($importer, $fields); case "retraction": return self::import_retraction($importer, $fields); @@ -676,6 +677,8 @@ class diaspora { } private function import_conversation($importer, $data) { + print_r($data); + die(); /* $guid = notags(unxmlify($xml->guid)); $subject = notags(unxmlify($xml->subject)); @@ -874,9 +877,8 @@ EOT; if ($parent_type !== "Post") return false; - // "positive" = "false" doesn't seem to be supported by Diaspora + // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora if ($positive === "false") { - logger("Received a like with positive set to 'false' - this shouldn't exist at all"); return false; } From ce5f6fae51ac6c246bb0371798994bef7e589d42 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 1 Mar 2016 19:14:45 +0100 Subject: [PATCH 146/273] Small bugfix: Fixes problen when version number contains linebreaks --- mod/admin.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mod/admin.php b/mod/admin.php index 7a16820c44..a98f464f81 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -283,14 +283,14 @@ function admin_page_federation(&$a) { // get a total count for the platform, the name and version of the // highest version and the protocol tpe $c = q('SELECT count(*) AS total, platform, network, version FROM gserver - WHERE platform LIKE "%s" AND last_contact > last_failure + WHERE platform LIKE "%s" AND last_contact > last_failure AND `version` != "" ORDER BY version ASC;', $p); $total = $total + $c[0]['total']; // what versions for that platform do we know at all? // again only the active nodes $v = q('SELECT count(*) AS total, version FROM gserver - WHERE last_contact > last_failure AND platform LIKE "%s" + WHERE last_contact > last_failure AND platform LIKE "%s" AND `version` != "" GROUP BY version ORDER BY version;', $p); @@ -344,6 +344,9 @@ function admin_page_federation(&$a) { $v = $newVv; } + foreach ($v as $key => $vv) + $v[$key]["version"] = trim(strip_tags($vv["version"])); + // the 3rd array item is needed for the JavaScript graphs as JS does // not like some characters in the names of variables... $counts[$p]=array($c[0], $v, str_replace(array(' ','%'),'',$p), $colors[$p]); From 411566f48b58135e6820b6557794806b077e5444 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 1 Mar 2016 19:17:01 +0100 Subject: [PATCH 147/273] API: Some small speed improvement when calling the home timeline. --- include/api.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/api.php b/include/api.php index 55e39e3583..699b066d25 100644 --- a/include/api.php +++ b/include/api.php @@ -1691,13 +1691,13 @@ `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` + FROM `item` FORCE INDEX (`uid_id`), `contact` WHERE `item`.`uid` = %d AND `verb` = '%s' AND NOT (`item`.`author-link` IN ('https://%s', 'http://%s')) - AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 + AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted` AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - AND `item`.`parent` IN (SELECT `iid` from thread where uid = %d AND `mention` AND !`ignored`) + AND NOT `contact`.`blocked` AND NOT `contact`.`pending` + AND `item`.`parent` IN (SELECT `iid` FROM `thread` WHERE `uid` = %d AND `mention` AND !`ignored`) $sql_extra AND `item`.`id`>%d ORDER BY `item`.`id` DESC LIMIT %d ,%d ", From f1dae26df8a9f46d683716b48b9b739f02824302 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 1 Mar 2016 23:21:56 +0100 Subject: [PATCH 148/273] Likes would now work with unlikes and with likes on comments. --- include/diaspora2.php | 48 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index e8ed80ee80..97d22b4b97 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -104,7 +104,8 @@ class diaspora { //return self::import_comment($importer, $sender, $fields); case "conversation": - return self::import_conversation($importer, $fields); + return true; + //return self::import_conversation($importer, $fields); case "like": return true; @@ -138,8 +139,8 @@ class diaspora { return self::import_retraction($importer, $fields); case "status_message": - return true; - //return self::import_status_message($importer, $fields, $msg, $data2); + //return true; + return self::import_status_message($importer, $fields); default: logger("Unknown message type ".$type); @@ -246,7 +247,7 @@ class diaspora { } // Only some message types have signatures. So we quit here for the other types. - if (!in_array($type, array("comment", "conversation", "message", "like"))) + if (!in_array($type, array("comment", "message", "like"))) return true; // No author_signature? This is a must, so we quit. @@ -691,7 +692,7 @@ class diaspora { $messages = $xml->message; if(! count($messages)) { - logger('diaspora_conversation: empty conversation'); + logger('empty conversation'); return; } @@ -874,14 +875,10 @@ EOT; $author = notags(unxmlify($data->author)); // likes on comments aren't supported by Diaspora - only on posts - if ($parent_type !== "Post") + // But maybe this will be supported in the future, so we will accept it. + if (!in_array($parent_type, array("Post", "Comment"))) return false; - // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora - if ($positive === "false") { - return false; - } - $contact = self::get_allowed_contact_by_handle($importer, $sender, true); if (!$contact) return false; @@ -902,6 +899,13 @@ EOT; // Fetch the contact id - if we know this contact $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]); + // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora + // We would accept this anyhow. + if ($positive === "true") + $verb = ACTIVITY_LIKE; + else + $verb = ACTIVITY_DISLIKE; + $datarray = array(); $datarray["uid"] = $importer["uid"]; @@ -920,7 +924,7 @@ EOT; $datarray["uri"] = $author.":".$guid; $datarray["type"] = "activity"; - $datarray["verb"] = ACTIVITY_LIKE; + $datarray["verb"] = $verb; $datarray["gravity"] = GRAVITY_LIKE; $datarray["parent-uri"] = $parent_item["uri"]; @@ -1429,18 +1433,18 @@ print_r($data); $created_at = notags(unxmlify($data->created_at)); $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::get_allowed_contact_by_handle($importer, $author, false); if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; - - /// @todo enable support for polls - // if ($data->poll) { - // print_r($data->poll); - // die("poll!\n"); - // } + //if (self::message_exists($importer["uid"], $guid)) + // return false; $address = array(); if ($data->location) @@ -1450,8 +1454,8 @@ print_r($data); $body = diaspora2bb($raw_message); if ($data->photo) - for ($i = 0; $i < count($data->photo); $i++) - $body = "[img]".$data->photo[$i]->remote_photo_path.$data->photo[$i]->remote_photo_name."[/img]\n".$body; + foreach ($data->photo AS $photo) + $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; $datarray = array(); From 66919761ab9ab60f1e6d3cac0810a6846e1e3a47 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 2 Mar 2016 23:28:20 +0100 Subject: [PATCH 149/273] Some work at the retractions. --- include/diaspora.php | 8 +- include/diaspora2.php | 381 +++++++++++++++++++++++++----------------- 2 files changed, 230 insertions(+), 159 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 9db9e6056e..0e0a860311 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -116,13 +116,13 @@ function diaspora_dispatch($importer,$msg,$attempt=1) { $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); } elseif($xmlbase->signed_retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); - //file_put_contents($tempfile, json_encode($data)); + $tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); } elseif($xmlbase->relayable_retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); - //file_put_contents($tempfile, json_encode($data)); + $tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); + file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg); } elseif($xmlbase->photo) { diff --git a/include/diaspora2.php b/include/diaspora2.php index 97d22b4b97..baf117b102 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -95,11 +95,11 @@ class diaspora { $type = $fields->getName(); switch ($type) { - case "account_deletion": + case "account_deletion": // Done return true; //return self::import_account_deletion($importer, $fields); - case "comment": + case "comment": // Done return true; //return self::import_comment($importer, $sender, $fields); @@ -107,40 +107,40 @@ class diaspora { return true; //return self::import_conversation($importer, $fields); - case "like": + case "like": // Done return true; //return self::import_like($importer, $sender, $fields); - case "message": + case "message": // Done return true; //return self::import_message($importer, $fields); case "participation": // Not implemented return self::import_participation($importer, $fields); - case "photo": + case "photo": // Not needed return self::import_photo($importer, $fields); case "poll_participation": // Not implemented return self::import_poll_participation($importer, $fields); - case "profile": + case "profile": // Done return true; //return self::import_profile($importer, $fields); case "request": return self::import_request($importer, $fields); - case "reshare": + case "reshare": // Done return true; //return self::import_reshare($importer, $fields); case "retraction": return self::import_retraction($importer, $fields); - case "status_message": - //return true; - return self::import_status_message($importer, $fields); + case "status_message": // Done + return true; + //return self::import_status_message($importer, $fields); default: logger("Unknown message type ".$type); @@ -567,25 +567,25 @@ class diaspora { } private function plink($addr, $guid) { - $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); + $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); - // Fallback - if (!$r) - return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; + // Fallback + if (!$r) + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; - // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table - // So we try another way as well. - $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); - if ($s) - $r[0]["network"] = $s[0]["network"]; + // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table + // So we try another way as well. + $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); + if ($s) + $r[0]["network"] = $s[0]["network"]; - if ($r[0]["network"] == NETWORK_DFRN) - return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); + if ($r[0]["network"] == NETWORK_DFRN) + return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); - if (self::is_redmatrix($r[0]["url"])) - return $r[0]["url"]."/?f=&mid=".$guid; + if (self::is_redmatrix($r[0]["url"])) + return $r[0]["url"]."/?f=&mid=".$guid; - return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; } private function import_account_deletion($importer, $data) { @@ -681,158 +681,158 @@ class diaspora { print_r($data); die(); /* - $guid = notags(unxmlify($xml->guid)); - $subject = notags(unxmlify($xml->subject)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $participant_handles = notags(unxmlify($xml->participant_handles)); - $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); + $guid = notags(unxmlify($xml->guid)); + $subject = notags(unxmlify($xml->subject)); + $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); + $participant_handles = notags(unxmlify($xml->participant_handles)); + $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); - $parent_uri = $diaspora_handle . ':' . $guid; + $parent_uri = $diaspora_handle . ':' . $guid; - $messages = $xml->message; + $messages = $xml->message; - if(! count($messages)) { - logger('empty conversation'); - return; - } + if(! count($messages)) { + logger('empty conversation'); + return; + } $contact = self::get_allowed_contact_by_handle($importer, $sender, true) if (!$contact) return false; - $conversation = null; + $conversation = null; - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - else { - $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ", - intval($importer['uid']), - dbesc($guid), - dbesc($diaspora_handle), - dbesc(datetime_convert('UTC','UTC',$created_at)), - dbesc(datetime_convert()), - dbesc($subject), - dbesc($participant_handles) - ); - if($r) - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - } - if(! $conversation) { - logger('diaspora_conversation: unable to create conversation.'); - return; - } + $c = q("select * from conv where uid = %d and guid = '%s' limit 1", + intval($importer["uid"]), + dbesc($guid) + ); + if(count($c)) + $conversation = $c[0]; + else { + $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ", + intval($importer["uid"]), + dbesc($guid), + dbesc($diaspora_handle), + dbesc(datetime_convert('UTC','UTC',$created_at)), + dbesc(datetime_convert()), + dbesc($subject), + dbesc($participant_handles) + ); + if($r) + $c = q("select * from conv where uid = %d and guid = '%s' limit 1", + intval($importer["uid"]), + dbesc($guid) + ); + if(count($c)) + $conversation = $c[0]; + } + if(! $conversation) { + logger('diaspora_conversation: unable to create conversation.'); + return; + } - foreach($messages as $mesg) { + foreach($messages as $mesg) { - $reply = 0; + $reply = 0; - $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))); - $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle)); - $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); - if($msg_conversation_guid != $guid) { - logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml); - continue; - } + $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))); + $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle)); + $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); + if($msg_conversation_guid != $guid) { + logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml); + continue; + } - $body = diaspora2bb($msg_text); - $message_id = $msg_diaspora_handle . ':' . $msg_guid; + $body = diaspora2bb($msg_text); + $message_id = $msg_diaspora_handle . ':' . $msg_guid; - $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; - $author_signature = base64_decode($msg_author_signature); + $author_signature = base64_decode($msg_author_signature); - if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) { - $person = $contact; - $key = $msg['key']; - } - else { - $person = find_diaspora_person_by_handle($msg_diaspora_handle); + if(strcasecmp($msg_diaspora_handle,$msg["author"]) == 0) { + $person = $contact; + $key = $msg["key"]; + } + else { + $person = find_diaspora_person_by_handle($msg_diaspora_handle); - if(is_array($person) && x($person,'pubkey')) - $key = $person['pubkey']; - else { - logger('diaspora_conversation: unable to find author details'); - continue; - } - } + if(is_array($person) && x($person,'pubkey')) + $key = $person["pubkey"]; + else { + logger('diaspora_conversation: unable to find author details'); + continue; + } + } - if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_conversation: verification failed.'); - continue; - } + if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { + logger('diaspora_conversation: verification failed.'); + continue; + } - if($msg_parent_author_signature) { - $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + if($msg_parent_author_signature) { + $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; - $parent_author_signature = base64_decode($msg_parent_author_signature); + $parent_author_signature = base64_decode($msg_parent_author_signature); - $key = $msg['key']; + $key = $msg["key"]; - if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_conversation: owner verification failed.'); - continue; - } - } + if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) { + logger('diaspora_conversation: owner verification failed.'); + continue; + } + } - $r = q("select id from mail where `uri` = '%s' limit 1", - dbesc($message_id) - ); - if(count($r)) { - logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG); - continue; - } + $r = q("select id from mail where `uri` = '%s' limit 1", + dbesc($message_id) + ); + if(count($r)) { + logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG); + continue; + } - q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer['uid']), - dbesc($msg_guid), - intval($conversation['id']), - dbesc($person['name']), - dbesc($person['photo']), - dbesc($person['url']), - intval($contact['id']), - dbesc($subject), - dbesc($body), - 0, - 0, - dbesc($message_id), - dbesc($parent_uri), - dbesc($msg_created_at) - ); + q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + intval($importer["uid"]), + dbesc($msg_guid), + intval($conversation["id"]), + dbesc($person["name"]), + dbesc($person["photo"]), + dbesc($person["url"]), + intval($contact["id"]), + dbesc($subject), + dbesc($body), + 0, + 0, + dbesc($message_id), + dbesc($parent_uri), + dbesc($msg_created_at) + ); - q("update conv set updated = '%s' where id = %d", - dbesc(datetime_convert()), - intval($conversation['id']) - ); + q("update conv set updated = '%s' where id = %d", + dbesc(datetime_convert()), + intval($conversation["id"]) + ); - notification(array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' =>$importer['uid'], - 'item' => array('subject' => $subject, 'body' => $body), - 'source_name' => $person['name'], - 'source_link' => $person['url'], - 'source_photo' => $person['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' - )); - } + notification(array( + 'type' => NOTIFY_MAIL, + 'notify_flags' => $importer["notify-flags"], + 'language' => $importer["language"], + 'to_name' => $importer["username"], + 'to_email' => $importer["email"], + 'uid' =>$importer["uid"], + 'item' => array('subject' => $subject, 'body' => $body), + 'source_name' => $person["name"], + 'source_link' => $person["url"], + 'source_photo' => $person["thumb"], + 'verb' => ACTIVITY_POST, + 'otype' => 'mail' + )); + } */ return true; } @@ -1420,7 +1420,78 @@ print_r($data); return $message_id; } + private function item_retraction($importer, $contact, $data) { + $target_guid = notags(unxmlify($data->target_guid)); + + $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) + return false; + + // Only delete it if the author really fits + if (!link_compare($r[0]["author-link"],$contact["url"])) + return false; + + // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case + q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($r[0]["id"]) + ); + delete_thread($r[0]["id"], $r[0]["parent-uri"]); + + // Now check if the retraction needs to be relayed by us + // + // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always + // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. + // The only item with `parent` and `id` as the parent id is the parent item. + $p = q("SELECT `origin` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($r[0]["parent"]), + intval($r[0]["parent"]) + ); + if(count($p)) { + if($p[0]["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($r[0]["id"]), + dbesc(json_encode($data)) + ); + + // the existence of parent_author_signature would have meant the parent_author or owner + // is already relaying. + logger("relaying retraction"); + + proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); + } + } + } + private function import_retraction($importer, $data) { + $target_type = notags(unxmlify($data->target_type)); + $author = notags(unxmlify($data->author)); + + $contact = self::get_contact_by_handle($importer["uid"], $author); + if (!$contact) { + logger("cannot find contact for author: ".$author); + return false; + } + + switch ($target_type) { + case "Comment": case "Like": case "StatusMessage": + self::item_retraction($importer, $contact, $data); + break; + + case "Person": + contact_remove($contact["id"]); + return true; + + default: + logger("Unknown target type ".$target_type); + } return true; } @@ -1434,11 +1505,11 @@ print_r($data); $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"); - } + //if ($data->poll) { + // foreach ($data->poll AS $poll) + // print_r($poll); + // die("poll!\n"); + //} $contact = self::get_allowed_contact_by_handle($importer, $author, false); if (!$contact) return false; From 5adecf5b50e11fb6016cc089426f74b8f930ed0b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 2 Mar 2016 23:39:50 +0100 Subject: [PATCH 150/273] Some beautification --- include/diaspora2.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index baf117b102..4d0e4c2cb8 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1524,16 +1524,16 @@ print_r($data); $body = diaspora2bb($raw_message); - if ($data->photo) + $datarray = array(); + + if ($data->photo) { foreach ($data->photo AS $photo) $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; - $datarray = array(); - - if($data->photo->remote_photo_path AND $data->photo->remote_photo_name) $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; - else { + } else { $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + // Add OEmbed and other information to the body if (!self::is_redmatrix($contact["url"])) $body = add_page_info_to_body($body, false, true); @@ -1541,6 +1541,7 @@ print_r($data); $str_tags = ""; + // This doesn't work. @todo Check if the "tag" field is filled in the "item_store" function. $cnt = preg_match_all("/@\[url=(.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER); if($cnt) { foreach($matches as $mtch) { From 2edf4548dca3ff2df4765be957766af91c4930d2 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 3 Mar 2016 23:34:17 +0100 Subject: [PATCH 151/273] Retractions could work now. --- include/diaspora2.php | 170 ++++++++++++++++++++++++++---------------- 1 file changed, 105 insertions(+), 65 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 4d0e4c2cb8..59a5c372db 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -13,6 +13,10 @@ require_once("include/socgraph.php"); require_once("include/group.php"); require_once("include/api.php"); +/** + * @brief This class contain functions to work with XML data + * + */ class xml { function from_array($array, &$xml) { @@ -45,12 +49,20 @@ class xml { } } } + /** * @brief This class contain functions to create and send Diaspora XML files * */ class diaspora { + /** + * @brief Dispatches public messages and find the fitting receivers + * + * @param array $msg The post that will be dispatched + * + * @return bool Was the message accepted? + */ public static function dispatch_public($msg) { $enabled = intval(get_config("system", "diaspora_enabled")); @@ -81,6 +93,14 @@ class diaspora { return $item_id; } + /** + * @brief Dispatches the different message types to the different functions + * + * @param array $importer Array of the importer user + * @param array $msg The post that will be dispatched + * + * @return bool Was the message accepted? + */ public static function dispatch($importer, $msg) { // The sender is the handle of the contact that sent the message. @@ -104,8 +124,8 @@ class diaspora { //return self::import_comment($importer, $sender, $fields); case "conversation": - return true; - //return self::import_conversation($importer, $fields); + //return true; + return self::import_conversation($importer, $fields); case "like": // Done return true; @@ -129,18 +149,20 @@ class diaspora { //return self::import_profile($importer, $fields); case "request": + //return true; return self::import_request($importer, $fields); case "reshare": // Done return true; //return self::import_reshare($importer, $fields); - case "retraction": - return self::import_retraction($importer, $fields); - - case "status_message": // Done + case "retraction": // Done return true; - //return self::import_status_message($importer, $fields); + //return self::import_retraction($importer, $sender, $fields); + + case "status_message": + //return true; + return self::import_status_message($importer, $fields); default: logger("Unknown message type ".$type); @@ -181,6 +203,7 @@ class diaspora { } $type = $element->getName(); + $orig_type = $type; // All retractions are handled identically from now on. // In the new version there will only be "retraction". @@ -235,7 +258,8 @@ class diaspora { $signed_data .= $entry; } - if (!in_array($fieldname, array("parent_author_signature", "target_author_signature"))) + if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR + ($orig_type == "relayable_retraction")) xml::copy($entry, $fields, $fieldname); } @@ -266,6 +290,13 @@ class diaspora { return rsa_verify($signed_data, $author_signature, $key, "sha256"); } + /** + * @brief Fetches the public key for a given handle + * + * @param string $handle The handle + * + * @return string The public key + */ private function get_key($handle) { logger("Fetching diaspora key for: ".$handle); @@ -276,6 +307,13 @@ class diaspora { return ""; } + /** + * @brief Fetches data for a given handle + * + * @param string $handle The handle + * + * @return array the queried data + */ private function get_person_by_handle($handle) { $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", @@ -306,6 +344,14 @@ class diaspora { return $person; } + /** + * @brief Updates the fcontact table + * + * @param array $arr The fcontact data + * @param bool $update Update or insert? + * + * @return string The id of the fcontact entry + */ private function add_fcontact($arr, $update = false) { /// @todo Remove this function from include/network.php @@ -477,13 +523,12 @@ class diaspora { if ($level > 5) return false; - // This will not work if the server is not a Diaspora server + // This will work for Diaspora and newer Friendica servers $source_url = $server."/p/".$guid.".xml"; $x = fetch_url($source_url); if(!$x) return false; - /// @todo - should maybe solved by the dispatcher $source_xml = parse_xml_string($x, false); if (!is_object($source_xml)) @@ -664,7 +709,7 @@ class diaspora { if($message_id AND $parent_item["origin"]) { // Formerly we stored the signed text, the signature and the author in different fields. - // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field. + // We now store the raw data so that we are more flexible. q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", intval($message_id), dbesc(json_encode($data)) @@ -678,6 +723,7 @@ class diaspora { } private function import_conversation($importer, $data) { + // @todo print_r($data); die(); /* @@ -934,13 +980,13 @@ EOT; $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); $message_id = item_store($datarray); - //print_r($datarray); + // print_r($datarray); // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { // Formerly we stored the signed text, the signature and the author in different fields. - // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field. + // We now store the raw data so that we are more flexible. q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", intval($message_id), dbesc(json_encode($data)) @@ -1125,7 +1171,8 @@ EOT; } private function import_request($importer, $data) { -print_r($data); + // @todo + print_r($data); /* $author = unxmlify($xml->author); $recipient = unxmlify($xml->recipient); @@ -1371,8 +1418,8 @@ print_r($data); if (!$contact) return false; -// if (self::message_exists($importer["uid"], $guid)) -// return false; + if (self::message_exists($importer["uid"], $guid)) + return false; $original_item = self::get_original_item($root_guid, $root_author, $author); if (!$original_item) @@ -1414,14 +1461,22 @@ print_r($data); $datarray["object-type"] = $original_item["object-type"]; self::fetch_guid($datarray); - //$message_id = item_store($datarray); - print_r($datarray); + $message_id = item_store($datarray); + // print_r($datarray); return $message_id; } private 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)); + + $person = self::get_person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author detail for ".$author); + return false; + } $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), @@ -1431,7 +1486,15 @@ print_r($data); return false; // Only delete it if the author really fits - if (!link_compare($r[0]["author-link"],$contact["url"])) + if (!link_compare($r[0]["author-link"],$person["url"])) + return false; + + // Check if the sender is the thread owner + $p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d", + intval($r[0]["parent"])); + + // Only delete it if the parent author really fits + if (!link_compare($p[0]["author-link"], $contact["url"])) return false; // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case @@ -1443,47 +1506,36 @@ print_r($data); delete_thread($r[0]["id"], $r[0]["parent-uri"]); // Now check if the retraction needs to be relayed by us - // - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("SELECT `origin` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($r[0]["parent"]), - intval($r[0]["parent"]) - ); - if(count($p)) { - if($p[0]["origin"]) { + if($p[0]["origin"]) { - // Formerly we stored the signed text, the signature and the author in different fields. - // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field. - q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", - intval($r[0]["id"]), - dbesc(json_encode($data)) - ); + // Formerly we stored the signed text, the signature and the author in different fields. + // We now store the raw data so that we are more flexible. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($r[0]["id"]), + dbesc(json_encode($data)) + ); - // the existence of parent_author_signature would have meant the parent_author or owner - // is already relaying. - logger("relaying retraction"); - - proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); - } + // notify others + proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); } } - private function import_retraction($importer, $data) { + private function import_retraction($importer, $sender, $data) { $target_type = notags(unxmlify($data->target_type)); - $author = notags(unxmlify($data->author)); - $contact = self::get_contact_by_handle($importer["uid"], $author); + $contact = self::get_contact_by_handle($importer["uid"], $sender); if (!$contact) { - logger("cannot find contact for author: ".$author); + logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]); return false; } switch ($target_type) { - case "Comment": case "Like": case "StatusMessage": - self::item_retraction($importer, $contact, $data); - break; + case "Comment": + case "Like": + case "Post": // "Post" will be supported in a future version + case "Reshare": + case "StatusMessage": + return self::item_retraction($importer, $contact, $data);; case "Person": contact_remove($contact["id"]); @@ -1491,6 +1543,7 @@ print_r($data); default: logger("Unknown target type ".$target_type); + return false; } return true; } @@ -1514,8 +1567,8 @@ print_r($data); if (!$contact) return false; - //if (self::message_exists($importer["uid"], $guid)) - // return false; + if (self::message_exists($importer["uid"], $guid)) + return false; $address = array(); if ($data->location) @@ -1539,18 +1592,6 @@ print_r($data); $body = add_page_info_to_body($body, false, true); } - $str_tags = ""; - - // This doesn't work. @todo Check if the "tag" field is filled in the "item_store" function. - $cnt = preg_match_all("/@\[url=(.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - if(strlen($str_tags)) - $str_tags .= ","; - $str_tags .= "@[url=".$mtch[1]."[/url]"; - } - } - $datarray["uid"] = $importer["uid"]; $datarray["contact-id"] = $contact["id"]; $datarray["network"] = NETWORK_DIASPORA; @@ -1573,7 +1614,6 @@ print_r($data); $datarray["body"] = $body; - $datarray["tag"] = $str_tags; if ($provider_display_name != "") $datarray["app"] = $provider_display_name; @@ -1588,8 +1628,8 @@ print_r($data); $datarray["coord"] = $address["lat"]." ".$address["lng"]; self::fetch_guid($datarray); - //$message_id = item_store($datarray); - print_r($datarray); + $message_id = item_store($datarray); + // print_r($datarray); logger("Stored item with message id ".$message_id, LOGGER_DEBUG); From d5c1ce490b8fb0cd5abfad4db3655016954217d5 Mon Sep 17 00:00:00 2001 From: Roland Haeder <roland@mxchange.org> Date: Fri, 4 Mar 2016 22:38:18 +0100 Subject: [PATCH 152/273] No processing if error or empty array Signed-off-by: Roland Haeder <roland@mxchange.org> --- include/poller.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/poller.php b/include/poller.php index 90e94ede9e..755862eb6b 100644 --- a/include/poller.php +++ b/include/poller.php @@ -205,6 +205,12 @@ function poller_max_connections_reached() { */ function poller_kill_stale_workers() { $r = q("SELECT `pid`, `executed` FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00'"); + + if (!is_array($r) || count($r) == 0) { + // No processing here needed + return; + } + foreach($r AS $pid) if (!posix_kill($pid["pid"], 0)) q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d", From ef3620c191add50b212fc5479abc0a6d1f04db6c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 4 Mar 2016 23:28:43 +0100 Subject: [PATCH 153/273] Conversations should work now too. --- include/diaspora2.php | 282 +++++++++++++++++++++--------------------- 1 file changed, 141 insertions(+), 141 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 59a5c372db..b834e3deb9 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -22,7 +22,7 @@ class xml { if (!is_object($xml)) { foreach($array as $key => $value) { - $root = new SimpleXMLElement('<'.$key.'/>'); + $root = new SimpleXMLElement("<".$key."/>"); array_to_xml($value, $root); $dom = dom_import_simplexml($root)->ownerDocument; @@ -82,7 +82,7 @@ class diaspora { dbesc(NETWORK_DIASPORA), dbesc($msg["author"]) ); - if(count($r)) { + if($r) { foreach($r as $rr) { logger("delivering to: ".$rr["username"]); self::dispatch($rr,$msg); @@ -125,7 +125,7 @@ class diaspora { case "conversation": //return true; - return self::import_conversation($importer, $fields); + return self::import_conversation($importer, $msg, $fields); case "like": // Done return true; @@ -320,7 +320,7 @@ class diaspora { dbesc(NETWORK_DIASPORA), dbesc($handle) ); - if (count($r)) { + if ($r) { $person = $r[0]; logger("In cache ".print_r($r,true), LOGGER_DEBUG); @@ -336,7 +336,7 @@ class diaspora { // Note that Friendica contacts will return a "Diaspora person" // if Diaspora connectivity is enabled on their server - if (count($r) AND ($r["network"] === NETWORK_DIASPORA)) { + if ($r AND ($r["network"] === NETWORK_DIASPORA)) { self::add_fcontact($r, $update); $person = $r; } @@ -415,17 +415,17 @@ class diaspora { dbesc($handle) ); - if ($r AND count($r)) + if ($r) return $r[0]; $handle_parts = explode("@", $handle); - $nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0]; + $nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0]; $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1", dbesc(NETWORK_DFRN), intval($uid), dbesc($nurl_sql) ); - if($r AND count($r)) + if($r) return $r[0]; return false; @@ -481,7 +481,7 @@ class diaspora { dbesc($guid) ); - if(count($r)) { + if($r) { logger("message ".$guid." already exists for user ".$uid); return false; } @@ -566,7 +566,7 @@ class diaspora { FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($uid), dbesc($guid)); - if(!count($r)) { + if(!$r) { $result = self::store_by_guid($guid, $contact["url"], $uid); if (!$result) { @@ -585,7 +585,7 @@ class diaspora { } } - if (!count($r)) { + if (!$r) { logger("parent item not found: parent: ".$guid." item: ".$guid); return false; } else @@ -722,62 +722,10 @@ class diaspora { return $message_id; } - private function import_conversation($importer, $data) { - // @todo - print_r($data); - die(); -/* - $guid = notags(unxmlify($xml->guid)); - $subject = notags(unxmlify($xml->subject)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $participant_handles = notags(unxmlify($xml->participant_handles)); - $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); - - $parent_uri = $diaspora_handle . ':' . $guid; - - $messages = $xml->message; - - if(! count($messages)) { - logger('empty conversation'); - return; - } - - $contact = self::get_allowed_contact_by_handle($importer, $sender, true) - if (!$contact) - return false; - - $conversation = null; - - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer["uid"]), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - else { - $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ", - intval($importer["uid"]), - dbesc($guid), - dbesc($diaspora_handle), - dbesc(datetime_convert('UTC','UTC',$created_at)), - dbesc(datetime_convert()), - dbesc($subject), - dbesc($participant_handles) - ); - if($r) - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer["uid"]), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - } - if(! $conversation) { - logger('diaspora_conversation: unable to create conversation.'); - return; - } - - foreach($messages as $mesg) { + private function import_conversation_message($importer, $contact, $data, $msg, $mesg) { + $guid = notags(unxmlify($data->guid)); + $subject = notags(unxmlify($data->subject)); + $author = notags(unxmlify($data->author)); $reply = 0; @@ -786,63 +734,64 @@ class diaspora { $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))); - $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle)); + $msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at))); + $msg_author = notags(unxmlify($mesg->diaspora_handle)); $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); + if($msg_conversation_guid != $guid) { - logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml); - continue; + logger("message conversation guid does not belong to the current conversation."); + return false; } $body = diaspora2bb($msg_text); - $message_id = $msg_diaspora_handle . ':' . $msg_guid; + $message_uri = $msg_author.":".$msg_guid; - $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + $author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid; $author_signature = base64_decode($msg_author_signature); - if(strcasecmp($msg_diaspora_handle,$msg["author"]) == 0) { + if(strcasecmp($msg_author,$msg["author"]) == 0) { $person = $contact; $key = $msg["key"]; - } - else { - $person = find_diaspora_person_by_handle($msg_diaspora_handle); + } else { + $person = self::get_person_by_handle($msg_author); - if(is_array($person) && x($person,'pubkey')) + if (is_array($person) && x($person, "pubkey")) $key = $person["pubkey"]; else { - logger('diaspora_conversation: unable to find author details'); - continue; + logger("unable to find author details"); + return false; } } - if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_conversation: verification failed.'); - continue; + 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_diaspora_handle . ';' . $msg_conversation_guid; + $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('diaspora_conversation: owner verification failed.'); - continue; + 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_id) + $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' LIMIT 1", + dbesc($message_uri) ); - if(count($r)) { - logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG); - continue; + if($r) { + logger("duplicate message already delivered.", LOGGER_DEBUG); + return false; } - q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) + VALUES (%d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", intval($importer["uid"]), dbesc($msg_guid), intval($conversation["id"]), @@ -854,32 +803,86 @@ class diaspora { dbesc($body), 0, 0, - dbesc($message_id), - dbesc($parent_uri), + dbesc($message_uri), + dbesc($author.":".$guid), dbesc($msg_created_at) ); - q("update conv set updated = '%s' where id = %d", + q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", dbesc(datetime_convert()), intval($conversation["id"]) ); notification(array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer["notify-flags"], - 'language' => $importer["language"], - 'to_name' => $importer["username"], - 'to_email' => $importer["email"], - 'uid' =>$importer["uid"], - 'item' => array('subject' => $subject, 'body' => $body), - 'source_name' => $person["name"], - 'source_link' => $person["url"], - 'source_photo' => $person["thumb"], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' + "type" => NOTIFY_MAIL, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" =>$importer["uid"], + "item" => array("subject" => $subject, "body" => $body), + "source_name" => $person["name"], + "source_link" => $person["url"], + "source_photo" => $person["thumb"], + "verb" => ACTIVITY_POST, + "otype" => "mail" )); } -*/ + + private function import_conversation($importer, $msg, $data) { + $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; + + if (!count($messages)) { + logger("empty conversation"); + return false; + } + + $contact = self::get_allowed_contact_by_handle($importer, $msg["author"], true); + if (!$contact) + return false; + + $conversation = null; + + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if($c) + $conversation = $c[0]; + else { + $r = q("INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`) + VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s')", + intval($importer["uid"]), + dbesc($guid), + dbesc($author), + dbesc(datetime_convert("UTC", "UTC", $created_at)), + dbesc(datetime_convert()), + dbesc($subject), + dbesc($participants) + ); + if($r) + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + + if($c) + $conversation = $c[0]; + } + if (!$conversation) { + logger("unable to create conversation."); + return; + } + + foreach($messages as $mesg) + self::import_conversation_message($importer, $contact, $data, $msg, $mesg); + return true; } @@ -1007,8 +1010,6 @@ EOT; $author = notags(unxmlify($data->author)); $conversation_guid = notags(unxmlify($data->conversation_guid)); - $parent_uri = $author.":".$parent_guid; - $contact = self::get_allowed_contact_by_handle($importer, $author, true); if (!$contact) return false; @@ -1019,7 +1020,7 @@ EOT; intval($importer["uid"]), dbesc($conversation_guid) ); - if(count($c)) + if($c) $conversation = $c[0]; else { logger("conversation not available."); @@ -1029,7 +1030,7 @@ EOT; $reply = 0; $body = diaspora2bb($text); - $message_id = $author.":".$guid; + $message_uri = $author.":".$guid; $person = self::get_person_by_handle($author); if (!$person) { @@ -1038,10 +1039,10 @@ EOT; } $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($message_id), + dbesc($message_uri), intval($importer["uid"]) ); - if(count($r)) { + if($r) { logger("duplicate message already delivered.", LOGGER_DEBUG); return false; } @@ -1059,8 +1060,8 @@ EOT; dbesc($body), 0, 1, - dbesc($message_id), - dbesc($parent_uri), + dbesc($message_uri), + dbesc($author.":".$parent_guid), dbesc($created_at) ); @@ -1174,8 +1175,8 @@ EOT; // @todo print_r($data); /* - $author = unxmlify($xml->author); - $recipient = unxmlify($xml->recipient); + $author = unxmlify($data->author); + $recipient = unxmlify($data->recipient); if (!$author || !$recipient) return; @@ -1200,7 +1201,7 @@ EOT; intval($importer["uid"]) ); - if((count($r)) && (!$r[0]["hide-friends"]) && (!$contact["hidden"]) && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) { + if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) { $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", intval($importer["uid"]) @@ -1208,7 +1209,7 @@ EOT; // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array - if(count($self) && $contact["rel"] == CONTACT_IS_FOLLOWER) { + if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) { $arr = array(); $arr["uri"] = $arr["parent-uri"] = item_new_uri(App::get_hostname(), $importer["uid"]); @@ -1224,17 +1225,16 @@ EOT; $arr["verb"] = ACTIVITY_FRIEND; $arr["object-type"] = ACTIVITY_OBJ_PERSON; - $A = '[url=' . $self[0]["url"] . "]' . $self[0]["name"] . '[/url]'; - $B = '[url=' . $contact["url"] . "]' . $contact["name"] . '[/url]'; - $BPhoto = '[url=' . $contact["url"] . "]' . '[img]' . $contact["thumb"] . '[/img][/url]'; + $A = '[url='.$self[0]["url"] . "]'.$self[0]["name"] .'[/url]'; + $B = '[url='.$contact["url"] . "]'.$contact["name"] .'[/url]'; + $BPhoto = '[url='.$contact["url"] . "]'.'[img]'.$contact["thumb"] .'[/img][/url]'; $arr["body"] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto; - $arr["object"] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $contact["name"] . '</title>' - . '<id>' . $contact["url"] . '/' . $contact["name"] . '</id>'; - $arr["object"] .= '<link>' . xmlify('<link rel="alternate" type="text/html" href="' . $contact["url"] . '" />' . "\n") -; - $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="' . $contact["thumb"] . '" />' . "\n"); - $arr["object"] .= '</link></object>' . "\n"; + $arr["object"] = '<object><type>'. ACTIVITY_OBJ_PERSON .'</type><title>'.$contact["name"] .'</title>' + .'<id>'.$contact["url"] .'/'.$contact["name"] .'</id>'; + $arr["object"] .= '<link>'. xmlify('<link rel="alternate" type="text/html" href="'.$contact["url"] .'" />'. "\n"); + $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="'.$contact["thumb"] .'" />'. "\n"); + $arr["object"] .= '</link></object>'. "\n"; $arr["last-child"] = 1; $arr["allow_cid"] = $user[0]["allow_cid"]; @@ -1256,12 +1256,12 @@ EOT; $ret = self::get_person_by_handle($author); - if((! count($ret)) || ($ret["network"] != NETWORK_DIASPORA)) { - logger('diaspora_request: Cannot resolve diaspora handle ' . $author . ' for ' . $recipient); + if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { + logger('Cannot resolve diaspora handle '.$author .' for '.$recipient); return; } - $batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) . '/receive/public'); + $batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) .'/receive/public'); @@ -1286,10 +1286,10 @@ EOT; // find the contact record we just created - $contact_record = diaspora_get_contact_by_handle($importer["uid"],$author); + $contact_record = self::get_contact_by_handle($importer["uid"],$author); if(! $contact_record) { - logger('diaspora_request: unable to locate newly created contact record.'); + logger('unable to locate newly created contact record.'); return; } @@ -1360,7 +1360,7 @@ EOT; FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", dbesc($guid)); - if(count($r)) { + if($r) { logger("reshared message ".$guid." already exists on system."); // Maybe it is already a reshared item? @@ -1371,23 +1371,23 @@ EOT; return $r[0]; } - if (!count($r)) { - $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); + if (!$r) { + $server = "https://".substr($orig_author, strpos($orig_author, "@") + 1); logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); $item_id = self::store_by_guid($guid, $server); if (!$item_id) { - $server = 'https://'.substr($author,strpos($author,'@')+1); + $server = "https://".substr($author, strpos($author, "@") + 1); logger("2nd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); $item = self::store_by_guid($guid, $server); } if (!$item_id) { - $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); + $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1); logger("3rd try: reshared message ".$guid." will be fetched from original server: ".$server); $item = self::store_by_guid($guid, $server); } if (!$item_id) { - $server = 'http://'.substr($author,strpos($author,'@')+1); + $server = "http://".substr($author, strpos($author, "@") + 1); logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); $item = self::store_by_guid($guid, $server); } From 5bbc8a14ceccf40defc37011308d2d549127f2f6 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 5 Mar 2016 00:27:44 +0100 Subject: [PATCH 154/273] "import" is now "receive" --- include/diaspora2.php | 311 +++++++++++++++++++++--------------------- 1 file changed, 157 insertions(+), 154 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index b834e3deb9..031058ab75 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -116,53 +116,53 @@ class diaspora { switch ($type) { case "account_deletion": // Done - return true; - //return self::import_account_deletion($importer, $fields); + //return true; + return self::receive_account_deletion($importer, $fields); case "comment": // Done - return true; - //return self::import_comment($importer, $sender, $fields); - - case "conversation": //return true; - return self::import_conversation($importer, $msg, $fields); + return self::receive_comment($importer, $sender, $fields); + + case "conversation": // Done + //return true; + return self::receive_conversation($importer, $msg, $fields); case "like": // Done - return true; - //return self::import_like($importer, $sender, $fields); + //return true; + return self::receive_like($importer, $sender, $fields); case "message": // Done - return true; - //return self::import_message($importer, $fields); + //return true; + return self::receive_message($importer, $fields); case "participation": // Not implemented - return self::import_participation($importer, $fields); + return self::receive_participation($importer, $fields); case "photo": // Not needed - return self::import_photo($importer, $fields); + return self::receive_photo($importer, $fields); case "poll_participation": // Not implemented - return self::import_poll_participation($importer, $fields); + return self::receive_poll_participation($importer, $fields); case "profile": // Done - return true; - //return self::import_profile($importer, $fields); + //return true; + return self::receive_profile($importer, $fields); case "request": //return true; - return self::import_request($importer, $fields); + return self::receive_request($importer, $fields); case "reshare": // Done - return true; - //return self::import_reshare($importer, $fields); + //return true; + return self::receive_reshare($importer, $fields); case "retraction": // Done - return true; - //return self::import_retraction($importer, $sender, $fields); - - case "status_message": //return true; - return self::import_status_message($importer, $fields); + return self::receive_retraction($importer, $sender, $fields); + + case "status_message": // Done + //return true; + return self::receive_status_message($importer, $fields); default: logger("Unknown message type ".$type); @@ -633,7 +633,7 @@ class diaspora { return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; } - private function import_account_deletion($importer, $data) { + private function receive_account_deletion($importer, $data) { $author = notags(unxmlify($data->author)); $contact = self::get_contact_by_handle($importer["uid"], $author); @@ -647,7 +647,7 @@ class diaspora { return true; } - private function import_comment($importer, $sender, $data) { + private function receive_comment($importer, $sender, $data) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); $text = unxmlify($data->text); @@ -722,7 +722,7 @@ class diaspora { return $message_id; } - private function import_conversation_message($importer, $contact, $data, $msg, $mesg) { + private function receive_conversation_message($importer, $contact, $data, $msg, $mesg) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); $author = notags(unxmlify($data->author)); @@ -735,7 +735,14 @@ class diaspora { $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))); - $msg_author = notags(unxmlify($mesg->diaspora_handle)); + + if ($mesg->diaspora_handle) + $msg_author = notags(unxmlify($mesg->diaspora_handle)); + elseif ($mesg->author) + $msg_author = notags(unxmlify($mesg->author)); + else + return false; + $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); if($msg_conversation_guid != $guid) { @@ -829,7 +836,7 @@ class diaspora { )); } - private function import_conversation($importer, $msg, $data) { + private function receive_conversation($importer, $msg, $data) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at))); @@ -881,7 +888,7 @@ class diaspora { } foreach($messages as $mesg) - self::import_conversation_message($importer, $contact, $data, $msg, $mesg); + self::receive_conversation_message($importer, $contact, $data, $msg, $mesg); return true; } @@ -916,7 +923,7 @@ EOT; return $obj; } - private function import_like($importer, $sender, $data) { + private function receive_like($importer, $sender, $data) { $positive = notags(unxmlify($data->positive)); $guid = notags(unxmlify($data->guid)); $parent_type = notags(unxmlify($data->parent_type)); @@ -1002,7 +1009,7 @@ EOT; return $message_id; } - private function import_message($importer, $data) { + private function receive_message($importer, $data) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); $text = unxmlify($data->text); @@ -1073,22 +1080,22 @@ EOT; return true; } - private function import_participation($importer, $data) { + private function receive_participation($importer, $data) { // I'm not sure if we can fully support this message type return true; } - private function import_photo($importer, $data) { + private function receive_photo($importer, $data) { // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well return true; } - private function import_poll_participation($importer, $data) { + private function receive_poll_participation($importer, $data) { // We don't support polls by now return true; } - private function import_profile($importer, $data) { + private function receive_profile($importer, $data) { $author = notags(unxmlify($data->author)); $contact = self::get_contact_by_handle($importer["uid"], $author); @@ -1171,23 +1178,7 @@ EOT; return true; } - private function import_request($importer, $data) { - // @todo - print_r($data); -/* - $author = unxmlify($data->author); - $recipient = unxmlify($data->recipient); - - if (!$author || !$recipient) - return; - - $contact = self::get_contact_by_handle($importer["uid"],$author); - - if($contact) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - + private function receive_request_make_friend($importer, $contact) { if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", intval(CONTACT_IS_FRIEND), @@ -1201,9 +1192,9 @@ EOT; intval($importer["uid"]) ); - if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) { + if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) { - $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", + $self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", intval($importer["uid"]) ); @@ -1225,16 +1216,16 @@ EOT; $arr["verb"] = ACTIVITY_FRIEND; $arr["object-type"] = ACTIVITY_OBJ_PERSON; - $A = '[url='.$self[0]["url"] . "]'.$self[0]["name"] .'[/url]'; - $B = '[url='.$contact["url"] . "]'.$contact["name"] .'[/url]'; - $BPhoto = '[url='.$contact["url"] . "]'.'[img]'.$contact["thumb"] .'[/img][/url]'; - $arr["body"] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto; + $A = "[url=".$self[0]["url"]."]".$self[0]["name"]."[/url]"; + $B = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; + $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]"; + $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto; - $arr["object"] = '<object><type>'. ACTIVITY_OBJ_PERSON .'</type><title>'.$contact["name"] .'</title>' - .'<id>'.$contact["url"] .'/'.$contact["name"] .'</id>'; - $arr["object"] .= '<link>'. xmlify('<link rel="alternate" type="text/html" href="'.$contact["url"] .'" />'. "\n"); - $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="'.$contact["thumb"] .'" />'. "\n"); - $arr["object"] .= '</link></object>'. "\n"; + $arr["object"] = "<object><type>".ACTIVITY_OBJ_PERSON."</type><title>".$contact["name"]."</title>" + ."<id>".$contact["url"]."/".$contact["name"]."</id>"; + $arr["object"] .= "<link>".xmlify('<link rel="alternate" type="text/html" href="'.$contact["url"].'" />'."\n"); + $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="'.$contact["thumb"].'" />'."\n"); + $arr["object"] .= "</link></object>\n"; $arr["last-child"] = 1; $arr["allow_cid"] = $user[0]["allow_cid"]; @@ -1244,111 +1235,123 @@ EOT; $i = item_store($arr); if($i) - proc_run('php',"include/notifier.php","activity","$i"); + proc_run("php", "include/notifier.php", "activity", $i); } } - - return; } - $ret = self::get_person_by_handle($author); + private function receive_request($importer, $data) { + $author = unxmlify($data->author); + $recipient = unxmlify($data->recipient); + if (!$author || !$recipient) + return; - if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { - logger('Cannot resolve diaspora handle '.$author .' for '.$recipient); - return; - } + $contact = self::get_contact_by_handle($importer["uid"],$author); - $batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) .'/receive/public'); + if($contact) { + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + self::receive_request_make_friend($importer, $contact); + return true; + } - $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) - VALUES ( %d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d) ", - intval($importer["uid"]), - dbesc($ret["network"]), - dbesc($ret["addr"]), - datetime_convert(), - dbesc($ret["url"]), - dbesc(normalise_link($ret["url"])), - dbesc($batch), - dbesc($ret["name"]), - dbesc($ret["nick"]), - dbesc($ret["photo"]), - dbesc($ret["pubkey"]), - dbesc($ret["notify"]), - dbesc($ret["poll"]), - 1, - 2 - ); + $ret = self::get_person_by_handle($author); - // find the contact record we just created + if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { + logger("Cannot resolve diaspora handle ".$author ." for ".$recipient); + return false; + } - $contact_record = self::get_contact_by_handle($importer["uid"],$author); + $batch = (($ret["batch"]) ? $ret["batch"] : implode("/", array_slice(explode("/", $ret["url"]), 0, 3))."/receive/public"); - if(! $contact_record) { - logger('unable to locate newly created contact record.'); - return; - } - - $g = q("select def_gid from user where uid = %d limit 1", - intval($importer["uid"]) - ); - if($g && intval($g[0]["def_gid"])) { - group_add_member($importer["uid"],'',$contact_record["id"],$g[0]["def_gid"]); - } - - if($importer["page-flags"] == PAGE_NORMAL) { - - $hash = random_string() . (string) time(); // Generate a confirm_key - - $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime` ) - VALUES ( %d, %d, %d, %d, '%s', '%s', '%s' )", + $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) + VALUES (%d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d)", intval($importer["uid"]), - intval($contact_record["id"]), - 0, - 0, - dbesc( t('Sharing notification from Diaspora network')), - dbesc($hash), - dbesc(datetime_convert()) - ); - } - else { - - // automatic friend approval - - update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]); - - // technically they are sharing with us (CONTACT_IS_SHARING), - // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX - // we are going to change the relationship and make them a follower. - - if($importer["page-flags"] == PAGE_FREELOVE) - $new_relation = CONTACT_IS_FRIEND; - else - $new_relation = CONTACT_IS_FOLLOWER; - - $r = q("UPDATE `contact` SET `rel` = %d, - `name-date` = '%s', - `uri-date` = '%s', - `blocked` = 0, - `pending` = 0, - `writable` = 1 - WHERE `id` = %d - ", - intval($new_relation), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($contact_record["id"]) + dbesc($ret["network"]), + dbesc($ret["addr"]), + datetime_convert(), + dbesc($ret["url"]), + dbesc(normalise_link($ret["url"])), + dbesc($batch), + dbesc($ret["name"]), + dbesc($ret["nick"]), + dbesc($ret["photo"]), + dbesc($ret["pubkey"]), + dbesc($ret["notify"]), + dbesc($ret["poll"]), + 1, + 2 ); - $u = q("select * from user where uid = %d limit 1",intval($importer["uid"])); - if($u) - $ret = diaspora_share($u[0],$contact_record); - } -*/ + // find the contact record we just created + + $contact_record = self::get_contact_by_handle($importer["uid"],$author); + + if (!$contact_record) { + logger("unable to locate newly created contact record."); + return; + } + + $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", + intval($importer["uid"]) + ); + + if($g && intval($g[0]["def_gid"])) + group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]); + + if($importer["page-flags"] == PAGE_NORMAL) { + + $hash = random_string().(string)time(); // Generate a confirm_key + + $ret = q("INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`) + VALUES (%d, %d, %d, %d, '%s', '%s', '%s')", + intval($importer["uid"]), + intval($contact_record["id"]), + 0, + 0, + dbesc(t("Sharing notification from Diaspora network")), + dbesc($hash), + dbesc(datetime_convert()) + ); + } else { + + // automatic friend approval + + update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]); + + // technically they are sharing with us (CONTACT_IS_SHARING), + // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX + // we are going to change the relationship and make them a follower. + + if($importer["page-flags"] == PAGE_FREELOVE) + $new_relation = CONTACT_IS_FRIEND; + else + $new_relation = CONTACT_IS_FOLLOWER; + + $r = q("UPDATE `contact` SET `rel` = %d, + `name-date` = '%s', + `uri-date` = '%s', + `blocked` = 0, + `pending` = 0, + `writable` = 1 + WHERE `id` = %d + ", + intval($new_relation), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($contact_record["id"]) + ); + + $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); + if($u) + $ret = diaspora_share($u[0], $contact_record); + } + return true; } @@ -1406,7 +1409,7 @@ EOT; return false; } - private function import_reshare($importer, $data) { + private function receive_reshare($importer, $data) { $root_author = notags(unxmlify($data->root_author)); $root_guid = notags(unxmlify($data->root_guid)); $guid = notags(unxmlify($data->guid)); @@ -1520,7 +1523,7 @@ EOT; } } - private function import_retraction($importer, $sender, $data) { + private function receive_retraction($importer, $sender, $data) { $target_type = notags(unxmlify($data->target_type)); $contact = self::get_contact_by_handle($importer["uid"], $sender); @@ -1548,7 +1551,7 @@ EOT; return true; } - private function import_status_message($importer, $data) { + private function receive_status_message($importer, $data) { $raw_message = unxmlify($data->raw_message); $guid = notags(unxmlify($data->guid)); From 2a4ebaa438c0e4c84e20d2567d708e472a7c7192 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 5 Mar 2016 01:30:49 +0100 Subject: [PATCH 155/273] Small cleanup --- include/diaspora2.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 031058ab75..939a816f40 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -553,7 +553,6 @@ class diaspora { $msg = array("message" => $x, "author" => $author); - // We don't really need this, but until the work is unfinished we better will keep this $msg["key"] = self::get_key($msg["author"]); return $msg; @@ -736,10 +735,12 @@ class diaspora { $msg_text = unxmlify($mesg->text); $msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at))); - if ($mesg->diaspora_handle) - $msg_author = notags(unxmlify($mesg->diaspora_handle)); - elseif ($mesg->author) + // "diaspora_handle" is the element name from the old version + // "author" is the element name from the new version + if ($mesg->author) $msg_author = notags(unxmlify($mesg->author)); + elseif ($mesg->diaspora_handle) + $msg_author = notags(unxmlify($mesg->diaspora_handle)); else return false; From 996cd8c24eb6770320a52d1629ed361d7d690a37 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 5 Mar 2016 13:29:01 +0100 Subject: [PATCH 156/273] The Font Awesome library is now updated, the replxy button is changed --- view/theme/vier/css/font2.css | 1 + view/theme/vier/font/FontAwesome.otf | Bin 85908 -> 109688 bytes view/theme/vier/font/fontawesome-webfont.eot | Bin 56006 -> 70807 bytes view/theme/vier/font/fontawesome-webfont.svg | 169 ++++++++++++++++-- view/theme/vier/font/fontawesome-webfont.ttf | Bin 112160 -> 142072 bytes view/theme/vier/font/fontawesome-webfont.woff | Bin 65452 -> 83588 bytes .../theme/vier/font/fontawesome-webfont.woff2 | Bin 0 -> 66624 bytes view/theme/vier/templates/wall_thread.tpl | 2 +- 8 files changed, 154 insertions(+), 18 deletions(-) mode change 100755 => 100644 view/theme/vier/font/fontawesome-webfont.eot mode change 100755 => 100644 view/theme/vier/font/fontawesome-webfont.svg mode change 100755 => 100644 view/theme/vier/font/fontawesome-webfont.ttf mode change 100755 => 100644 view/theme/vier/font/fontawesome-webfont.woff create mode 100644 view/theme/vier/font/fontawesome-webfont.woff2 diff --git a/view/theme/vier/css/font2.css b/view/theme/vier/css/font2.css index 4300a910c0..a62cffc00c 100644 --- a/view/theme/vier/css/font2.css +++ b/view/theme/vier/css/font2.css @@ -228,6 +228,7 @@ li.icon.icon-large:before { .icon-key:before { content: "\f084"; } .icon.gears:before { content: "\f085"; } .icon-comments:before { content: "\f086"; } +.icon-commenting:before { content: "\f27a"; } .icon.like:before { content: "\f087"; } .icon.dislike:before { content: "\f088"; } .icon-star-half:before { content: "\f089"; } diff --git a/view/theme/vier/font/FontAwesome.otf b/view/theme/vier/font/FontAwesome.otf index 81c9ad949b47f64afeca5642ee2494b6e3147f44..3ed7f8b48ad9bfab52eb03822fefcd6b77d2e680 100644 GIT binary patch delta 55115 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 delta 31586 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 diff --git a/view/theme/vier/font/fontawesome-webfont.eot b/view/theme/vier/font/fontawesome-webfont.eot old mode 100755 new mode 100644 index 84677bc0c5f37f1fac9d87548c4554b5c91717cf..9b6afaedc0fd7aaf927a07f82da9c11022251b8b GIT binary patch literal 70807 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 Ppt>MT0000000000CGV>t literal 56006 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�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Χ}4ZzXD^bT4LX>3gK@n9cUCK-Z-%h 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 diff --git a/view/theme/vier/font/fontawesome-webfont.svg b/view/theme/vier/font/fontawesome-webfont.svg old mode 100755 new mode 100644 index d907b25ae6..d05688e9e2 --- a/view/theme/vier/font/fontawesome-webfont.svg +++ b/view/theme/vier/font/fontawesome-webfont.svg @@ -147,14 +147,14 @@ <glyph unicode="" horiz-adv-x="1792" d="M1683 205l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5t19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M1683 728l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5t19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5z" /> <glyph unicode="" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " /> -<glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M640 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1536 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1664 1088v-512q0 -24 -16.5 -42.5t-40.5 -21.5l-1044 -122q13 -60 13 -70q0 -16 -24 -64h920q26 0 45 -19t19 -45 t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 11 8 31.5t16 36t21.5 40t15.5 29.5l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t19.5 -15.5t13 -24.5t8 -26t5.5 -29.5t4.5 -26h1201q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" /> <glyph unicode="" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" /> <glyph unicode="" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" /> <glyph unicode="" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" /> <glyph unicode="" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" d="M1536 160q0 -119 -84.5 -203.5t-203.5 -84.5h-192v608h203l30 224h-233v143q0 54 28 83t96 29l132 1v207q-96 9 -180 9q-136 0 -218 -80.5t-82 -225.5v-166h-224v-224h224v-608h-544q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5v-960z" /> +<glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-188v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-532q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960z" /> <glyph unicode="" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" /> <glyph unicode="" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" /> @@ -219,8 +219,8 @@ <glyph unicode="" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" /> <glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" /> -<glyph unicode="" d="M829 318q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5zM755 863q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5 t-57.5 96.5t-17.5 106q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107zM861 1120l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95 q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83zM1152 672h128v64h-128v128h-64v-128 h-128v-64h128v-160h64v160zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> -<glyph unicode="" horiz-adv-x="1664" d="M735 740q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4 q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65t-59.5 -61.5t-24.5 -66zM589 836q38 0 78 16.5t66 43.5q53 57 53 159q0 58 -17 125t-48.5 129.5 t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26zM591 -37q58 0 111.5 13t99 39t73 73t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -48 2 q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5zM1401 839h213v-108h-213v-219h-105v219h-212v108h212v217h105v-217z" /> +<glyph unicode="" d="M917 631q0 26 -6 64h-362v-132h217q-3 -24 -16.5 -50t-37.5 -53t-66.5 -44.5t-96.5 -17.5q-99 0 -169 71t-70 171t70 171t169 71q92 0 153 -59l104 101q-108 100 -257 100q-160 0 -272 -112.5t-112 -271.5t112 -271.5t272 -112.5q165 0 266.5 105t101.5 270zM1262 585 h109v110h-109v110h-110v-110h-110v-110h110v-110h110v110zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1437 623q0 -208 -87 -370.5t-248 -254t-369 -91.5q-149 0 -285 58t-234 156t-156 234t-58 285t58 285t156 234t234 156t285 58q286 0 491 -192l-199 -191q-117 113 -292 113q-123 0 -227.5 -62t-165.5 -168.5t-61 -232.5t61 -232.5t165.5 -168.5t227.5 -62 q83 0 152.5 23t114.5 57.5t78.5 78.5t49 83t21.5 74h-416v252h692q12 -63 12 -122zM2304 745v-210h-209v-209h-210v209h-209v210h209v209h210v-209h209z" /> <glyph unicode="" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" /> <glyph unicode="" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" /> @@ -275,7 +275,7 @@ <glyph unicode="" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" /> <glyph unicode="" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" /> <glyph unicode="" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" /> -<glyph unicode="" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M526 142q0 -53 -37.5 -90.5t-90.5 -37.5q-52 0 -90 38t-38 90q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1522 142q0 -52 -38 -90t-90 -38q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM558 1138q0 -66 -47 -113t-113 -47t-113 47t-47 113t47 113t113 47t113 -47t47 -113z M1728 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1088 1344q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1618 1138q0 -93 -66 -158.5t-158 -65.5q-93 0 -158.5 65.5t-65.5 158.5 q0 92 65.5 158t158.5 66q92 0 158 -66t66 -158z" /> <glyph unicode="" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" /> <glyph unicode="" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" /> @@ -362,7 +362,7 @@ <glyph unicode="" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" /> <glyph unicode="" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" /> -<glyph unicode="" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" /> +<glyph unicode="" d="M1289 -96h-1118v480h-160v-640h1438v640h-160v-480zM347 428l33 157l783 -165l-33 -156zM450 802l67 146l725 -339l-67 -145zM651 1158l102 123l614 -513l-102 -123zM1048 1536l477 -641l-128 -96l-477 641zM330 65v159h800v-159h-800z" /> <glyph unicode="" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" /> <glyph unicode="" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" /> <glyph unicode="" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> @@ -399,7 +399,7 @@ <glyph unicode="" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" /> -<glyph unicode="" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M1292 898q10 216 -161 222q-231 8 -312 -261q44 19 82 19q85 0 74 -96q-4 -57 -74 -167t-105 -110q-43 0 -82 169q-13 54 -45 255q-30 189 -160 177q-59 -7 -164 -100l-81 -72l-81 -72l52 -67q76 52 87 52q57 0 107 -179q15 -55 45 -164.5t45 -164.5q68 -179 164 -179 q157 0 383 294q220 283 226 444zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> <glyph unicode="" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="2176" d="M620 416q-110 -64 -268 -64h-128v64h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5t38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40 t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113zM1739 668q53 -36 53 -92t-53 -92l81 -30q68 48 68 122t-68 122zM625 400h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96l-93 464h29 q157 0 273 64zM352 816h-29l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64z" /> @@ -410,9 +410,9 @@ <glyph unicode="" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" /> <glyph unicode="" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" /> <glyph unicode="" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" /> -<glyph unicode="" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" /> -<glyph unicode="" horiz-adv-x="1984" d="M831 572q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41t96.5 -41t40.5 -98zM1292 711q56 0 96.5 -41t40.5 -98q0 -56 -40.5 -96t-96.5 -40q-57 0 -98 40t-41 96q0 57 41.5 98t97.5 41zM1984 722q0 -62 -31 -114t-83 -82q5 -33 5 -61 q0 -121 -68.5 -230.5t-197.5 -193.5q-125 -82 -285.5 -125.5t-335.5 -43.5q-176 0 -336.5 43.5t-284.5 125.5q-129 84 -197.5 193t-68.5 231q0 29 5 66q-48 31 -77 81.5t-29 109.5q0 94 66 160t160 66q83 0 148 -55q248 158 592 164l134 423q4 14 17.5 21.5t28.5 4.5 l347 -82q22 50 68.5 81t102.5 31q77 0 131.5 -54.5t54.5 -131.5t-54.5 -132t-131.5 -55q-76 0 -130.5 54t-55.5 131l-315 74l-116 -366q327 -14 560 -166q64 58 151 58q94 0 160 -66t66 -160zM1664 1459q-45 0 -77 -32t-32 -77t32 -77t77 -32t77 32t32 77t-32 77t-77 32z M77 722q0 -67 51 -111q49 131 180 235q-36 25 -82 25q-62 0 -105.5 -43.5t-43.5 -105.5zM1567 105q112 73 171.5 166t59.5 194t-59.5 193.5t-171.5 165.5q-116 75 -265.5 115.5t-313.5 40.5t-313.5 -40.5t-265.5 -115.5q-112 -73 -171.5 -165.5t-59.5 -193.5t59.5 -194 t171.5 -166q116 -75 265.5 -115.5t313.5 -40.5t313.5 40.5t265.5 115.5zM1850 605q57 46 57 117q0 62 -43.5 105.5t-105.5 43.5q-49 0 -86 -28q131 -105 178 -238zM1258 237q11 11 27 11t27 -11t11 -27.5t-11 -27.5q-99 -99 -319 -99h-2q-220 0 -319 99q-11 11 -11 27.5 t11 27.5t27 11t27 -11q77 -77 265 -77h2q188 0 265 77z" /> -<glyph unicode="" d="M950 393q7 7 17.5 7t17.5 -7t7 -18t-7 -18q-65 -64 -208 -64h-1h-1q-143 0 -207 64q-8 7 -8 18t8 18q7 7 17.5 7t17.5 -7q49 -51 172 -51h1h1q122 0 173 51zM671 613q0 -37 -26 -64t-63 -27t-63 27t-26 64t26 63t63 26t63 -26t26 -63zM1214 1049q-29 0 -50 21t-21 50 q0 30 21 51t50 21q30 0 51 -21t21 -51q0 -29 -21 -50t-51 -21zM1216 1408q132 0 226 -94t94 -227v-894q0 -133 -94 -227t-226 -94h-896q-132 0 -226 94t-94 227v894q0 133 94 227t226 94h896zM1321 596q35 14 57 45.5t22 70.5q0 51 -36 87.5t-87 36.5q-60 0 -98 -48 q-151 107 -375 115l83 265l206 -49q1 -50 36.5 -85t84.5 -35q50 0 86 35.5t36 85.5t-36 86t-86 36q-36 0 -66 -20.5t-45 -53.5l-227 54q-9 2 -17.5 -2.5t-11.5 -14.5l-95 -302q-224 -4 -381 -113q-36 43 -93 43q-51 0 -87 -36.5t-36 -87.5q0 -37 19.5 -67.5t52.5 -45.5 q-7 -25 -7 -54q0 -98 74 -181.5t201.5 -132t278.5 -48.5q150 0 277.5 48.5t201.5 132t74 181.5q0 27 -6 54zM971 702q37 0 63 -26t26 -63t-26 -64t-63 -27t-63 27t-26 64t26 63t63 26z" /> +<glyph unicode="" d="M768 750h725q12 -67 12 -128q0 -217 -91 -387.5t-259.5 -266.5t-386.5 -96q-157 0 -299 60.5t-245 163.5t-163.5 245t-60.5 299t60.5 299t163.5 245t245 163.5t299 60.5q300 0 515 -201l-209 -201q-123 119 -306 119q-129 0 -238.5 -65t-173.5 -176.5t-64 -243.5 t64 -243.5t173.5 -176.5t238.5 -65q87 0 160 24t120 60t82 82t51.5 87t22.5 78h-436v264z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1095 369q16 -16 0 -31q-62 -62 -199 -62t-199 62q-16 15 0 31q6 6 15 6t15 -6q48 -49 169 -49q120 0 169 49q6 6 15 6t15 -6zM788 550q0 -37 -26 -63t-63 -26t-63.5 26t-26.5 63q0 38 26.5 64t63.5 26t63 -26.5t26 -63.5zM1183 550q0 -37 -26.5 -63t-63.5 -26t-63 26 t-26 63t26 63.5t63 26.5t63.5 -26t26.5 -64zM1434 670q0 49 -35 84t-85 35t-86 -36q-130 90 -311 96l63 283l200 -45q0 -37 26 -63t63 -26t63.5 26.5t26.5 63.5t-26.5 63.5t-63.5 26.5q-54 0 -80 -50l-221 49q-19 5 -25 -16l-69 -312q-180 -7 -309 -97q-35 37 -87 37 q-50 0 -85 -35t-35 -84q0 -35 18.5 -64t49.5 -44q-6 -27 -6 -56q0 -142 140 -243t337 -101q198 0 338 101t140 243q0 32 -7 57q30 15 48 43.5t18 63.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191 t348 71t348 -71t286 -191t191 -286t71 -348z" /> +<glyph unicode="" d="M939 407q13 -13 0 -26q-53 -53 -171 -53t-171 53q-13 13 0 26q5 6 13 6t13 -6q42 -42 145 -42t145 42q5 6 13 6t13 -6zM676 563q0 -31 -23 -54t-54 -23t-54 23t-23 54q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1014 563q0 -31 -23 -54t-54 -23t-54 23t-23 54 q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1229 666q0 42 -30 72t-73 30q-42 0 -73 -31q-113 78 -267 82l54 243l171 -39q1 -32 23.5 -54t53.5 -22q32 0 54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5q-48 0 -69 -43l-189 42q-17 5 -21 -13l-60 -268q-154 -6 -265 -83 q-30 32 -74 32q-43 0 -73 -30t-30 -72q0 -30 16 -55t42 -38q-5 -25 -5 -48q0 -122 120 -208.5t289 -86.5q170 0 290 86.5t120 208.5q0 25 -6 49q25 13 40.5 37.5t15.5 54.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" /> <glyph unicode="" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" /> <glyph unicode="" d="M1472 160v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480h704v-704h480q93 0 158.5 65.5t65.5 158.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" /> @@ -438,7 +438,7 @@ <glyph unicode="" horiz-adv-x="2048" d="M1824 640q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-96v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-1024v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5 t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h128q98 0 179 -63.5t104 -157.5l105 -419h28zM320 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM516 640h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5z M1728 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47z" /> <glyph unicode="" d="M1504 64q0 -26 -19 -45t-45 -19h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45t19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384 q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45z" /> <glyph unicode="" d="M1127 326q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5zM1223 541q0 40 -35 61q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64 q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5zM1331 789q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37 q159 0 309.5 -34t253.5 -95q21 -12 40 -12q29 0 50.5 20.5t21.5 51.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> -<glyph unicode="" d="M1397 1408q58 0 98.5 -40.5t40.5 -98.5v-1258q0 -58 -40.5 -98.5t-98.5 -40.5h-1258q-58 0 -98.5 40.5t-40.5 98.5v1258q0 58 40.5 98.5t98.5 40.5h1258zM1465 11v1258q0 28 -20 48t-48 20h-1258q-28 0 -48 -20t-20 -48v-1258q0 -28 20 -48t48 -20h1258q28 0 48 20t20 48 zM694 749l188 -387l533 145v-496q0 -7 -5.5 -12.5t-12.5 -5.5h-1258q-7 0 -12.5 5.5t-5.5 12.5v141l711 195l-212 439q4 1 12 2.5t12 1.5q170 32 303.5 21.5t221 -46t143.5 -94.5q27 -28 -25 -42q-64 -16 -256 -62l-97 198q-111 7 -240 -16zM1397 1287q7 0 12.5 -5.5 t5.5 -12.5v-428q-85 30 -188 52q-294 64 -645 12l-18 -3l-65 134h-233l85 -190q-132 -51 -230 -137v560q0 7 5.5 12.5t12.5 5.5h1258zM286 387q-14 -3 -26 4.5t-14 21.5q-24 203 166 305l129 -270z" /> +<glyph unicode="" horiz-adv-x="1024" d="M1024 1233l-303 -582l24 -31h279v-415h-507l-44 -30l-142 -273l-30 -30h-301v303l303 583l-24 30h-279v415h507l44 30l142 273l30 30h301v-303z" /> <glyph unicode="" horiz-adv-x="2304" d="M784 164l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17l-14 -523l14 -241q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23zM1080 193l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6l-10 -579q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11 q11 0 20 9q9 7 9 20zM35 533l20 -128l-20 -126q-2 -9 -9 -9t-9 9l-17 126l17 128q2 9 9 9t9 -9zM121 612l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10l-23 202l23 207q0 9 9 9q8 0 10 -9zM401 159zM213 650l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11l-21 237l21 245 q2 12 12 12q11 0 11 -12zM307 657l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13l-21 244l21 252q0 13 13 13q12 0 14 -13zM401 639l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5l-20 246l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15zM784 164zM495 785 l21 -380l-21 -246q0 -7 -5 -12.5t-12 -5.5q-16 0 -18 18l-18 246l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5zM589 871l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19l-16 244l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5zM687 911l18 -506l-18 -242 q-2 -21 -22 -21q-19 0 -21 21l-16 242l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5zM1079 169v0v0zM881 915l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18l-14 239l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18zM980 896l14 -492l-14 -236q0 -11 -8 -19 t-19 -8t-19 8t-9 19l-12 236l12 492q1 12 9 20t19 8t18.5 -8t8.5 -20zM1192 404l-14 -231v0q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114l-6 117l12 636v3q2 15 12 24q9 7 20 7q8 0 15 -5q14 -8 16 -26zM2304 423q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22v899 q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201z" /> <glyph unicode="" d="M768 768q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 0q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127 t443 -43zM768 384q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 1536q208 0 385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128v128q0 69 103 128t280 93.5t385 34.5z" /> <glyph unicode="" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M894 465q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4q52 85 107 197 q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM716 974q-15 -42 -2 -132q1 7 7 44q0 3 7 43q1 4 4 8 q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83zM1238 329q-24 24 -140 24q76 -28 124 -28q14 0 18 1q0 1 -2 3z" /> @@ -454,12 +454,12 @@ <glyph unicode="" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" /> <glyph unicode="" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1760 640q0 -176 -68.5 -336t-184 -275.5t-275.5 -184t-336 -68.5t-336 68.5t-275.5 184t-184 275.5t-68.5 336q0 213 97 398.5t265 305.5t374 151v-228q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5 t136.5 204t51 248.5q0 230 -145.5 406t-366.5 221v228q206 -31 374 -151t265 -305.5t97 -398.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" /> <glyph unicode="" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> <glyph unicode="" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M595 22q0 100 -165 100q-158 0 -158 -104q0 -101 172 -101q151 0 151 105zM536 777q0 61 -30 102t-89 41q-124 0 -124 -145q0 -135 124 -135q119 0 119 137zM805 1101v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58 q0 -31 22.5 -51.5t58 -32t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47 q98 0 218 47zM1123 220h-222q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134zM1724 442v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6v190h96v76q0 54 -6 89h227q-6 -41 -6 -165h171 v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33zM1148 1389q0 -58 -39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5z" /> -<glyph unicode="" d="M825 547l343 588h-150q-21 -39 -63.5 -118.5t-68 -128.5t-59.5 -118.5t-60 -128.5h-3q-21 48 -44.5 97t-52 105.5t-46.5 92t-54 104.5t-49 95h-150l323 -589v-435h134v436zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> <glyph unicode="" horiz-adv-x="1280" d="M842 964q0 -80 -57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5t29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5 t136.5 -56.5t56.5 -136.5zM1223 953q0 -158 -78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153t-153 102t-186 38t-186 -38t-153 -102t-102 -153 t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5z" /> <glyph unicode="" horiz-adv-x="1792" d="M270 730q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5t3.5 -5t2 -3.5 q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9t-98 20 t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20q-18 -41 -54.5 -74.5 t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100q0 275 252 466z" /> <glyph unicode="" horiz-adv-x="2048" d="M580 1075q0 41 -25 66t-66 25q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5zM1323 568q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51zM1087 1075q0 41 -24.5 66t-65.5 25 q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5zM1722 568q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51zM1456 965q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5 q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5zM2048 404q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109 q-150 -37 -218 -37q-169 0 -311 70.5t-223.5 191.5t-81.5 264t81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5z" /> @@ -483,13 +483,13 @@ <glyph unicode="" horiz-adv-x="2048" d="M1024 1024h-384v-384h384v384zM1152 384v-128h-640v128h640zM1152 1152v-640h-640v640h640zM1792 384v-128h-512v128h512zM1792 640v-128h-512v128h512zM1792 896v-128h-512v128h512zM1792 1152v-128h-512v128h512zM256 192v960h-128v-960q0 -26 19 -45t45 -19t45 19 t19 45zM1920 192v1088h-1536v-1088q0 -33 -11 -64h1483q26 0 45 19t19 45zM2048 1408v-1216q0 -80 -56 -136t-136 -56h-1664q-80 0 -136 56t-56 136v1088h256v128h1792z" /> <glyph unicode="" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" /> <glyph unicode="" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" /> -<glyph unicode="" horiz-adv-x="1792" d="M1112 1090q0 159 -237 159h-70q-32 0 -59.5 -21.5t-34.5 -52.5l-63 -276q-2 -5 -2 -16q0 -24 17 -39.5t41 -15.5h53q69 0 128.5 13t112.5 41t83.5 81.5t30.5 126.5zM1716 938q0 -265 -220 -428q-219 -161 -612 -161h-61q-32 0 -59 -21.5t-34 -52.5l-73 -316 q-8 -36 -40.5 -61.5t-69.5 -25.5h-213q-31 0 -53 20t-22 51q0 10 13 65h151q34 0 64 23.5t38 56.5l73 316q8 33 37.5 57t63.5 24h61q390 0 607 160t217 421q0 129 -51 207q183 -92 183 -335zM1533 1123q0 -264 -221 -428q-218 -161 -612 -161h-60q-32 0 -59.5 -22t-34.5 -53 l-73 -315q-8 -36 -40 -61.5t-69 -25.5h-214q-31 0 -52.5 19.5t-21.5 51.5q0 8 2 20l300 1301q8 36 40.5 61.5t69.5 25.5h444q68 0 125 -4t120.5 -15t113.5 -30t96.5 -50.5t77.5 -74t49.5 -103.5t18.5 -136z" /> +<glyph unicode="" d="M1519 890q18 -84 -4 -204q-87 -444 -565 -444h-44q-25 0 -44 -16.5t-24 -42.5l-4 -19l-55 -346l-2 -15q-5 -26 -24.5 -42.5t-44.5 -16.5h-251q-21 0 -33 15t-9 36q9 56 26.5 168t26.5 168t27 167.5t27 167.5q5 37 43 37h131q133 -2 236 21q175 39 287 144q102 95 155 246 q24 70 35 133q1 6 2.5 7.5t3.5 1t6 -3.5q79 -59 98 -162zM1347 1172q0 -107 -46 -236q-80 -233 -302 -315q-113 -40 -252 -42q0 -1 -90 -1l-90 1q-100 0 -118 -96q-2 -8 -85 -530q-1 -10 -12 -10h-295q-22 0 -36.5 16.5t-11.5 38.5l232 1471q5 29 27.5 48t51.5 19h598 q34 0 97.5 -13t111.5 -32q107 -41 163.5 -123t56.5 -196z" /> <glyph unicode="" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" /> <glyph unicode="" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> <glyph unicode="" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> <glyph unicode="" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" /> <glyph unicode="" horiz-adv-x="2304" d="M119 854h89l-45 108zM740 328l74 79l-70 79h-163v-49h142v-55h-142v-54h159zM898 406l99 -110v217zM1186 453q0 33 -40 33h-84v-69h83q41 0 41 36zM1475 457q0 29 -42 29h-82v-61h81q43 0 43 32zM1197 923q0 29 -42 29h-82v-60h81q43 0 43 31zM1656 854h89l-44 108z M699 1009v-271h-66v212l-94 -212h-57l-94 212v-212h-132l-25 60h-135l-25 -60h-70l116 271h96l110 -257v257h106l85 -184l77 184h108zM1255 453q0 -20 -5.5 -35t-14 -25t-22.5 -16.5t-26 -10t-31.5 -4.5t-31.5 -1t-32.5 0.5t-29.5 0.5v-91h-126l-80 90l-83 -90h-256v271h260 l80 -89l82 89h207q109 0 109 -89zM964 794v-56h-217v271h217v-57h-152v-49h148v-55h-148v-54h152zM2304 235v-229q0 -55 -38.5 -94.5t-93.5 -39.5h-2040q-55 0 -93.5 39.5t-38.5 94.5v678h111l25 61h55l25 -61h218v46l19 -46h113l20 47v-47h541v99l10 1q10 0 10 -14v-86h279 v23q23 -12 55 -18t52.5 -6.5t63 0.5t51.5 1l25 61h56l25 -61h227v58l34 -58h182v378h-180v-44l-25 44h-185v-44l-23 44h-249q-69 0 -109 -22v22h-172v-22q-24 22 -73 22h-628l-43 -97l-43 97h-198v-44l-22 44h-169l-78 -179v391q0 55 38.5 94.5t93.5 39.5h2040 q55 0 93.5 -39.5t38.5 -94.5v-678h-120q-51 0 -81 -22v22h-177q-55 0 -78 -22v22h-316v-22q-31 22 -87 22h-209v-22q-23 22 -91 22h-234l-54 -58l-50 58h-349v-378h343l55 59l52 -59h211v89h21q59 0 90 13v-102h174v99h8q8 0 10 -2t2 -10v-87h529q57 0 88 24v-24h168 q60 0 95 17zM1546 469q0 -23 -12 -43t-34 -29q25 -9 34 -26t9 -46v-54h-65v45q0 33 -12 43.5t-46 10.5h-69v-99h-65v271h154q48 0 77 -15t29 -58zM1269 936q0 -24 -12.5 -44t-33.5 -29q26 -9 34.5 -25.5t8.5 -46.5v-53h-65q0 9 0.5 26.5t0 25t-3 18.5t-8.5 16t-17.5 8.5 t-29.5 3.5h-70v-98h-64v271l153 -1q49 0 78 -14.5t29 -57.5zM1798 327v-56h-216v271h216v-56h-151v-49h148v-55h-148v-54zM1372 1009v-271h-66v271h66zM2065 357q0 -86 -102 -86h-126v58h126q34 0 34 25q0 16 -17 21t-41.5 5t-49.5 3.5t-42 22.5t-17 55q0 39 26 60t66 21 h130v-57h-119q-36 0 -36 -25q0 -16 17.5 -20.5t42 -4t49 -2.5t42 -21.5t17.5 -54.5zM2304 407v-101q-24 -35 -88 -35h-125v58h125q33 0 33 25q0 13 -12.5 19t-31 5.5t-40 2t-40 8t-31 24t-12.5 48.5q0 39 26.5 60t66.5 21h129v-57h-118q-36 0 -36 -25q0 -20 29 -22t68.5 -5 t56.5 -26zM2139 1008v-270h-92l-122 203v-203h-132l-26 60h-134l-25 -60h-75q-129 0 -129 133q0 138 133 138h63v-59q-7 0 -28 1t-28.5 0.5t-23 -2t-21.5 -6.5t-14.5 -13.5t-11.5 -23t-3 -33.5q0 -38 13.5 -58t49.5 -20h29l92 213h97l109 -256v256h99l114 -188v188h66z" /> -<glyph unicode="" horiz-adv-x="2304" d="M322 689h-15q-19 0 -19 18q0 28 19 85q5 15 15 19.5t28 4.5q77 0 77 -49q0 -41 -30.5 -59.5t-74.5 -18.5zM664 528q-47 0 -47 29q0 62 123 62l3 -3q-5 -88 -79 -88zM1438 687h-15q-19 0 -19 19q0 28 19 85q5 15 14.5 19t28.5 4q77 0 77 -49q0 -41 -30.5 -59.5 t-74.5 -18.5zM1780 527q-47 0 -47 30q0 62 123 62l3 -3q-5 -89 -79 -89zM373 894h-128q-8 0 -14.5 -4t-8.5 -7.5t-7 -12.5q-3 -7 -45 -190t-42 -192q0 -7 5.5 -12.5t13.5 -5.5h62q25 0 32.5 34.5l15 69t32.5 34.5q47 0 87.5 7.5t80.5 24.5t63.5 52.5t23.5 84.5 q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM719 798q-38 0 -74 -6q-2 0 -8.5 -1t-9 -1.5l-7.5 -1.5t-7.5 -2t-6.5 -3t-6.5 -4t-5 -5t-4.5 -7t-4 -9q-9 -29 -9 -39t9 -10q5 0 21.5 5t19.5 6q30 8 58 8q74 0 74 -36q0 -11 -10 -14q-8 -2 -18 -3t-21.5 -1.5t-17.5 -1.5 q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5q0 -38 26 -59.5t64 -21.5q24 0 45.5 6.5t33 13t38.5 23.5q-3 -7 -3 -15t5.5 -13.5t12.5 -5.5h56q1 1 7 3.5t7.5 3.5t5 3.5t5 5.5t2.5 8l45 194q4 13 4 30q0 81 -145 81zM1247 793h-74q-22 0 -39 -23q-5 -7 -29.5 -51 t-46.5 -81.5t-26 -38.5l-5 4q0 77 -27 166q-1 5 -3.5 8.5t-6 6.5t-6.5 5t-8.5 3t-8.5 1.5t-9.5 1t-9 0.5h-10h-8.5q-38 0 -38 -21l1 -5q5 -53 25 -151t25 -143q2 -16 2 -24q0 -19 -30.5 -61.5t-30.5 -58.5q0 -13 40 -13q61 0 76 25l245 415q10 20 10 26q0 9 -8 9zM1489 892 h-129q-18 0 -29 -23q-6 -13 -46.5 -191.5t-40.5 -190.5q0 -20 43 -20h7.5h9h9t9.5 1t8.5 2t8.5 3t6.5 4.5t5.5 6t3 8.5l21 91q2 10 10.5 17t19.5 7q47 0 87.5 7t80.5 24.5t63.5 52.5t23.5 84q0 36 -14.5 61t-41 36.5t-53.5 15.5t-62 4zM1835 798q-26 0 -74 -6 q-38 -6 -48 -16q-7 -8 -11 -19q-8 -24 -8 -39q0 -10 8 -10q1 0 41 12q30 8 58 8q74 0 74 -36q0 -12 -10 -14q-4 -1 -57 -7q-38 -4 -64.5 -10t-56.5 -19.5t-45.5 -39t-15.5 -62.5t26 -58.5t64 -21.5q24 0 45 6t34 13t38 24q-3 -15 -3 -16q0 -5 2 -8.5t6.5 -5.5t8 -3.5 t10.5 -2t9.5 -0.5h9.5h8q42 0 48 25l45 194q3 15 3 31q0 81 -145 81zM2157 889h-55q-25 0 -33 -40q-10 -44 -36.5 -167t-42.5 -190v-5q0 -16 16 -18h1h57q10 0 18.5 6.5t10.5 16.5l83 374h-1l1 5q0 7 -5.5 12.5t-13.5 5.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048 q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M745 630q0 -37 -25.5 -61.5t-62.5 -24.5q-29 0 -46.5 16t-17.5 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM1530 779q0 -42 -22 -57t-66 -15l-32 -1l17 107q2 11 13 11h18q22 0 35 -2t25 -12.5t12 -30.5zM1881 630q0 -36 -25.5 -61t-61.5 -25q-29 0 -47 16 t-18 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM513 801q0 59 -38.5 85.5t-100.5 26.5h-160q-19 0 -21 -19l-65 -408q-1 -6 3 -11t10 -5h76q20 0 22 19l18 110q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM822 489l41 261q1 6 -3 11t-10 5h-76 q-14 0 -17 -33q-27 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q28 0 58 12t48 32q-4 -12 -4 -21q0 -16 13 -16h69q19 0 22 19zM1269 752q0 5 -4 9.5t-9 4.5h-77q-11 0 -18 -10l-106 -156l-44 150q-5 16 -22 16h-75q-5 0 -9 -4.5t-4 -9.5q0 -2 19.5 -59 t42 -123t23.5 -70q-82 -112 -82 -120q0 -13 13 -13h77q11 0 18 10l255 368q2 2 2 7zM1649 801q0 59 -38.5 85.5t-100.5 26.5h-159q-20 0 -22 -19l-65 -408q-1 -6 3 -11t10 -5h82q12 0 16 13l18 116q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM1958 489 l41 261q1 6 -3 11t-10 5h-76q-14 0 -17 -33q-26 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q29 0 59 12t47 32q0 -1 -2 -9t-2 -12q0 -16 13 -16h69q19 0 22 19zM2176 898v1q0 14 -13 14h-74q-11 0 -13 -11l-65 -416l-1 -2q0 -5 4 -9.5t10 -4.5h66 q19 0 21 19zM392 764q-5 -35 -26 -46t-60 -11l-33 -1l17 107q2 11 13 11h19q40 0 58 -11.5t12 -48.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> <glyph unicode="" horiz-adv-x="2304" d="M1597 633q0 -69 -21 -106q-19 -35 -52 -35q-23 0 -41 9v224q29 30 57 30q57 0 57 -122zM2035 669h-110q6 98 56 98q51 0 54 -98zM476 534q0 59 -33 91.5t-101 57.5q-36 13 -52 24t-16 25q0 26 38 26q58 0 124 -33l18 112q-67 32 -149 32q-77 0 -123 -38q-48 -39 -48 -109 q0 -58 32.5 -90.5t99.5 -56.5q39 -14 54.5 -25.5t15.5 -27.5q0 -31 -48 -31q-29 0 -70 12.5t-72 30.5l-18 -113q72 -41 168 -41q81 0 129 37q51 41 51 117zM771 749l19 111h-96v135l-129 -21l-18 -114l-46 -8l-17 -103h62v-219q0 -84 44 -120q38 -30 111 -30q32 0 79 11v118 q-32 -7 -44 -7q-42 0 -42 50v197h77zM1087 724v139q-15 3 -28 3q-32 0 -55.5 -16t-33.5 -46l-10 56h-131v-471h150v306q26 31 82 31q16 0 26 -2zM1124 389h150v471h-150v-471zM1746 638q0 122 -45 179q-40 52 -111 52q-64 0 -117 -56l-8 47h-132v-645l150 25v151 q36 -11 68 -11q83 0 134 56q61 65 61 202zM1278 986q0 33 -23 56t-56 23t-56 -23t-23 -56t23 -56.5t56 -23.5t56 23.5t23 56.5zM2176 629q0 113 -48 176q-50 64 -144 64q-96 0 -151.5 -66t-55.5 -180q0 -128 63 -188q55 -55 161 -55q101 0 160 40l-16 103q-57 -31 -128 -31 q-43 0 -63 19q-23 19 -28 66h248q2 14 2 52zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> <glyph unicode="" horiz-adv-x="2048" d="M1558 684q61 -356 298 -556q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5zM1024 -176q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5zM2026 1424q8 -10 7.5 -23.5t-10.5 -22.5 l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5 l418 363q10 8 23.5 7t21.5 -11z" /> <glyph unicode="" horiz-adv-x="2048" d="M1040 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM503 315l877 760q-42 88 -132.5 146.5t-223.5 58.5q-93 0 -169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -384 -137 -645zM1856 128 q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5l149 129h757q-166 187 -227 459l111 97q61 -356 298 -556zM1942 1520l84 -96q8 -10 7.5 -23.5t-10.5 -22.5l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161 q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5l418 363q10 8 23.5 7t21.5 -11z" /> @@ -513,8 +513,143 @@ <glyph unicode="" horiz-adv-x="2048" d="M785 528h207q-14 -158 -98.5 -248.5t-214.5 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-203q-5 64 -35.5 99t-81.5 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t40 -51.5t66 -18q95 0 109 139zM1497 528h206 q-14 -158 -98 -248.5t-214 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-204q-4 64 -35 99t-81 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t39.5 -51.5t65.5 -18q49 0 76.5 38t33.5 101zM1856 647q0 207 -15.5 307 t-60.5 161q-6 8 -13.5 14t-21.5 15t-16 11q-86 63 -697 63q-625 0 -710 -63q-5 -4 -17.5 -11.5t-21 -14t-14.5 -14.5q-45 -60 -60 -159.5t-15 -308.5q0 -208 15 -307.5t60 -160.5q6 -8 15 -15t20.5 -14t17.5 -12q44 -33 239.5 -49t470.5 -16q610 0 697 65q5 4 17 11t20.5 14 t13.5 16q46 60 61 159t15 309zM2048 1408v-1536h-2048v1536h2048z" /> <glyph unicode="" d="M992 912v-496q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v496q0 112 -80 192t-192 80h-272v-1152q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v1344q0 14 9 23t23 9h464q135 0 249 -66.5t180.5 -180.5t66.5 -249zM1376 1376v-880q0 -135 -66.5 -249t-180.5 -180.5 t-249 -66.5h-464q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-768h272q112 0 192 80t80 192v880q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" /> <glyph unicode="" d="M1311 694v-114q0 -24 -13.5 -38t-37.5 -14h-202q-24 0 -38 14t-14 38v114q0 24 14 38t38 14h202q24 0 37.5 -14t13.5 -38zM821 464v250q0 53 -32.5 85.5t-85.5 32.5h-133q-68 0 -96 -52q-28 52 -96 52h-130q-53 0 -85.5 -32.5t-32.5 -85.5v-250q0 -22 21 -22h55 q22 0 22 22v230q0 24 13.5 38t38.5 14h94q24 0 38 -14t14 -38v-230q0 -22 21 -22h54q22 0 22 22v230q0 24 14 38t38 14h97q24 0 37.5 -14t13.5 -38v-230q0 -22 22 -22h55q21 0 21 22zM1410 560v154q0 53 -33 85.5t-86 32.5h-264q-53 0 -86 -32.5t-33 -85.5v-410 q0 -21 22 -21h55q21 0 21 21v180q31 -42 94 -42h191q53 0 86 32.5t33 85.5zM1536 1176v-1072q0 -96 -68 -164t-164 -68h-1072q-96 0 -164 68t-68 164v1072q0 96 68 164t164 68h1072q96 0 164 -68t68 -164z" /> -<glyph unicode="" horiz-adv-x="1792" /> -<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" d="M915 450h-294l147 551zM1001 128h311l-324 1024h-440l-324 -1024h311l383 314zM1536 1120v-960q0 -118 -85 -203t-203 -85h-960q-118 0 -203 85t-85 203v960q0 118 85 203t203 85h960q118 0 203 -85t85 -203z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2048 641q0 -21 -13 -36.5t-33 -19.5l-205 -356q3 -9 3 -18q0 -20 -12.5 -35.5t-32.5 -19.5l-193 -337q3 -8 3 -16q0 -23 -16.5 -40t-40.5 -17q-25 0 -41 18h-400q-17 -20 -43 -20t-43 20h-399q-17 -20 -43 -20q-23 0 -40 16.5t-17 40.5q0 8 4 20l-193 335 q-20 4 -32.5 19.5t-12.5 35.5q0 9 3 18l-206 356q-20 5 -32.5 20.5t-12.5 35.5q0 21 13.5 36.5t33.5 19.5l199 344q0 1 -0.5 3t-0.5 3q0 36 34 51l209 363q-4 10 -4 18q0 24 17 40.5t40 16.5q26 0 44 -21h396q16 21 43 21t43 -21h398q18 21 44 21q23 0 40 -16.5t17 -40.5 q0 -6 -4 -18l207 -358q23 -1 39 -17.5t16 -38.5q0 -13 -7 -27l187 -324q19 -4 31.5 -19.5t12.5 -35.5zM1063 -158h389l-342 354h-143l-342 -354h360q18 16 39 16t39 -16zM112 654q1 -4 1 -13q0 -10 -2 -15l208 -360q2 0 4.5 -1t5.5 -2.5l5 -2.5l188 199v347l-187 194 q-13 -8 -29 -10zM986 1438h-388l190 -200l554 200h-280q-16 -16 -38 -16t-38 16zM1689 226q1 6 5 11l-64 68l-17 -79h76zM1583 226l22 105l-252 266l-296 -307l63 -64h463zM1495 -142l16 28l65 310h-427l333 -343q8 4 13 5zM578 -158h5l342 354h-373v-335l4 -6q14 -5 22 -13 zM552 226h402l64 66l-309 321l-157 -166v-221zM359 226h163v189l-168 -177q4 -8 5 -12zM358 1051q0 -1 0.5 -2t0.5 -2q0 -16 -8 -29l171 -177v269zM552 1121v-311l153 -157l297 314l-223 236zM556 1425l-4 -8v-264l205 74l-191 201q-6 -2 -10 -3zM1447 1438h-16l-621 -224 l213 -225zM1023 946l-297 -315l311 -319l296 307zM688 634l-136 141v-284zM1038 270l-42 -44h85zM1374 618l238 -251l132 624l-3 5l-1 1zM1718 1018q-8 13 -8 29v2l-216 376q-5 1 -13 5l-437 -463l310 -327zM522 1142v223l-163 -282zM522 196h-163l163 -283v283zM1607 196 l-48 -227l130 227h-82zM1729 266l207 361q-2 10 -2 14q0 1 3 16l-171 296l-129 -612l77 -82q5 3 15 7z" /> +<glyph unicode="" d="M0 856q0 131 91.5 226.5t222.5 95.5h742l352 358v-1470q0 -132 -91.5 -227t-222.5 -95h-780q-131 0 -222.5 95t-91.5 227v790zM1232 102l-176 180v425q0 46 -32 79t-78 33h-484q-46 0 -78 -33t-32 -79v-492q0 -46 32.5 -79.5t77.5 -33.5h770z" /> +<glyph unicode="" d="M934 1386q-317 -121 -556 -362.5t-358 -560.5q-20 89 -20 176q0 208 102.5 384.5t278.5 279t384 102.5q82 0 169 -19zM1203 1267q93 -65 164 -155q-389 -113 -674.5 -400.5t-396.5 -676.5q-93 72 -155 162q112 386 395 671t667 399zM470 -67q115 356 379.5 622t619.5 384 q40 -92 54 -195q-292 -120 -516 -345t-343 -518q-103 14 -194 52zM1536 -125q-193 50 -367 115q-135 -84 -290 -107q109 205 274 370.5t369 275.5q-21 -152 -101 -284q65 -175 115 -370z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1893 1144l155 -1272q-131 0 -257 57q-200 91 -393 91q-226 0 -374 -148q-148 148 -374 148q-193 0 -393 -91q-128 -57 -252 -57h-5l155 1272q224 127 482 127q233 0 387 -106q154 106 387 106q258 0 482 -127zM1398 157q129 0 232 -28.5t260 -93.5l-124 1021 q-171 78 -368 78q-224 0 -374 -141q-150 141 -374 141q-197 0 -368 -78l-124 -1021q105 43 165.5 65t148.5 39.5t178 17.5q202 0 374 -108q172 108 374 108zM1438 191l-55 907q-211 -4 -359 -155q-152 155 -374 155q-176 0 -336 -66l-114 -941q124 51 228.5 76t221.5 25 q209 0 374 -102q172 107 374 102z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1500 165v733q0 21 -15 36t-35 15h-93q-20 0 -35 -15t-15 -36v-733q0 -20 15 -35t35 -15h93q20 0 35 15t15 35zM1216 165v531q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-531q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM924 165v429q0 20 -15 35t-35 15h-101 q-20 0 -35 -15t-15 -35v-429q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM632 165v362q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-362q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM2048 311q0 -166 -118 -284t-284 -118h-1244q-166 0 -284 118t-118 284 q0 116 63 214.5t168 148.5q-10 34 -10 73q0 113 80.5 193.5t193.5 80.5q102 0 180 -67q45 183 194 300t338 117q149 0 275 -73.5t199.5 -199.5t73.5 -275q0 -66 -14 -122q135 -33 221 -142.5t86 -247.5z" /> +<glyph unicode="" d="M0 1536h1536v-1392l-776 -338l-760 338v1392zM1436 209v926h-1336v-926l661 -294zM1436 1235v201h-1336v-201h1336zM181 937v-115h-37v115h37zM181 789v-115h-37v115h37zM181 641v-115h-37v115h37zM181 493v-115h-37v115h37zM181 345v-115h-37v115h37zM207 202l15 34 l105 -47l-15 -33zM343 142l15 34l105 -46l-15 -34zM478 82l15 34l105 -46l-15 -34zM614 23l15 33l104 -46l-15 -34zM797 10l105 46l15 -33l-105 -47zM932 70l105 46l15 -34l-105 -46zM1068 130l105 46l15 -34l-105 -46zM1203 189l105 47l15 -34l-105 -46zM259 1389v-36h-114 v36h114zM421 1389v-36h-115v36h115zM583 1389v-36h-115v36h115zM744 1389v-36h-114v36h114zM906 1389v-36h-114v36h114zM1068 1389v-36h-115v36h115zM1230 1389v-36h-115v36h115zM1391 1389v-36h-114v36h114zM181 1049v-79h-37v115h115v-36h-78zM421 1085v-36h-115v36h115z M583 1085v-36h-115v36h115zM744 1085v-36h-114v36h114zM906 1085v-36h-114v36h114zM1068 1085v-36h-115v36h115zM1230 1085v-36h-115v36h115zM1355 970v79h-78v36h115v-115h-37zM1355 822v115h37v-115h-37zM1355 674v115h37v-115h-37zM1355 526v115h37v-115h-37zM1355 378 v115h37v-115h-37zM1355 230v115h37v-115h-37zM760 265q-129 0 -221 91.5t-92 221.5q0 129 92 221t221 92q130 0 221.5 -92t91.5 -221q0 -130 -91.5 -221.5t-221.5 -91.5zM595 646q0 -36 19.5 -56.5t49.5 -25t64 -7t64 -2t49.5 -9t19.5 -30.5q0 -49 -112 -49q-97 0 -123 51 h-3l-31 -63q67 -42 162 -42q29 0 56.5 5t55.5 16t45.5 33t17.5 53q0 46 -27.5 69.5t-67.5 27t-79.5 3t-67 5t-27.5 25.5q0 21 20.5 33t40.5 15t41 3q34 0 70.5 -11t51.5 -34h3l30 58q-3 1 -21 8.5t-22.5 9t-19.5 7t-22 7t-20 4.5t-24 4t-23 1q-29 0 -56.5 -5t-54 -16.5 t-43 -34t-16.5 -53.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M863 504q0 112 -79.5 191.5t-191.5 79.5t-191 -79.5t-79 -191.5t79 -191t191 -79t191.5 79t79.5 191zM1726 505q0 112 -79 191t-191 79t-191.5 -79t-79.5 -191q0 -113 79.5 -192t191.5 -79t191 79.5t79 191.5zM2048 1314v-1348q0 -44 -31.5 -75.5t-76.5 -31.5h-1832 q-45 0 -76.5 31.5t-31.5 75.5v1348q0 44 31.5 75.5t76.5 31.5h431q44 0 76 -31.5t32 -75.5v-161h754v161q0 44 32 75.5t76 31.5h431q45 0 76.5 -31.5t31.5 -75.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1430 953zM1690 749q148 0 253 -98.5t105 -244.5q0 -157 -109 -261.5t-267 -104.5q-85 0 -162 27.5t-138 73.5t-118 106t-109 126.5t-103.5 132.5t-108.5 126t-117 106t-136 73.5t-159 27.5q-154 0 -251.5 -91.5t-97.5 -244.5q0 -157 104 -250t263 -93q100 0 208 37.5 t193 98.5q5 4 21 18.5t30 24t22 9.5q14 0 24.5 -10.5t10.5 -24.5q0 -24 -60 -77q-101 -88 -234.5 -142t-260.5 -54q-133 0 -245.5 58t-180 165t-67.5 241q0 205 141.5 341t347.5 136q120 0 226.5 -43.5t185.5 -113t151.5 -153t139 -167.5t133.5 -153.5t149.5 -113 t172.5 -43.5q102 0 168.5 61.5t66.5 162.5q0 95 -64.5 159t-159.5 64q-30 0 -81.5 -18.5t-68.5 -18.5q-20 0 -35.5 15t-15.5 35q0 18 8.5 57t8.5 59q0 159 -107.5 263t-266.5 104q-58 0 -111.5 -18.5t-84 -40.5t-55.5 -40.5t-33 -18.5q-15 0 -25.5 10.5t-10.5 25.5 q0 19 25 46q59 67 147 103.5t182 36.5q191 0 318 -125.5t127 -315.5q0 -37 -4 -66q57 15 115 15z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1216 832q0 26 -19 45t-45 19h-128v128q0 26 -19 45t-45 19t-45 -19t-19 -45v-128h-128q-26 0 -45 -19t-19 -45t19 -45t45 -19h128v-128q0 -26 19 -45t45 -19t45 19t19 45v128h128q26 0 45 19t19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="1664" d="M1280 832q0 26 -19 45t-45 19t-45 -19l-147 -146v293q0 26 -19 45t-45 19t-45 -19t-19 -45v-293l-147 146q-19 19 -45 19t-45 -19t-19 -45t19 -45l256 -256q19 -19 45 -19t45 19l256 256q19 19 19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" /> +<glyph unicode="" horiz-adv-x="2048" d="M212 768l623 -665l-300 665h-323zM1024 -4l349 772h-698zM538 896l204 384h-262l-288 -384h346zM1213 103l623 665h-323zM683 896h682l-204 384h-274zM1510 896h346l-288 384h-262zM1651 1382l384 -512q14 -18 13 -41.5t-17 -40.5l-960 -1024q-18 -20 -47 -20t-47 20 l-960 1024q-16 17 -17 40.5t13 41.5l384 512q18 26 51 26h1152q33 0 51 -26z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1811 -19q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83 q19 19 45 19t45 -19l83 -83zM237 19q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -82l83 82q19 19 45 19t45 -19l83 -82l64 64v293l-210 314q-17 26 -7 56.5t40 40.5l177 58v299h128v128h256v128h256v-128h256v-128h128v-299l177 -58q30 -10 40 -40.5t-7 -56.5l-210 -314 v-293l19 18q19 19 45 19t45 -19l83 -82l83 82q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83zM640 1152v-128l384 128l384 -128v128h-128v128h-512v-128h-128z" /> +<glyph unicode="" d="M576 0l96 448l-96 128l-128 64zM832 0l128 640l-128 -64l-96 -128zM992 1010q-2 4 -4 6q-10 8 -96 8q-70 0 -167 -19q-7 -2 -21 -2t-21 2q-97 19 -167 19q-86 0 -96 -8q-2 -2 -4 -6q2 -18 4 -27q2 -3 7.5 -6.5t7.5 -10.5q2 -4 7.5 -20.5t7 -20.5t7.5 -17t8.5 -17t9 -14 t12 -13.5t14 -9.5t17.5 -8t20.5 -4t24.5 -2q36 0 59 12.5t32.5 30t14.5 34.5t11.5 29.5t17.5 12.5h12q11 0 17.5 -12.5t11.5 -29.5t14.5 -34.5t32.5 -30t59 -12.5q13 0 24.5 2t20.5 4t17.5 8t14 9.5t12 13.5t9 14t8.5 17t7.5 17t7 20.5t7.5 20.5q2 7 7.5 10.5t7.5 6.5 q2 9 4 27zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 61 4.5 118t19 125.5t37.5 123.5t63.5 103.5t93.5 74.5l-90 220h214q-22 64 -22 128q0 12 2 32q-194 40 -194 96q0 57 210 99q17 62 51.5 134t70.5 114q32 37 76 37q30 0 84 -31t84 -31t84 31 t84 31q44 0 76 -37q36 -42 70.5 -114t51.5 -134q210 -42 210 -99q0 -56 -194 -96q7 -81 -20 -160h214l-82 -225q63 -33 107.5 -96.5t65.5 -143.5t29 -151.5t8 -148.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M2301 500q12 -103 -22 -198.5t-99 -163.5t-158.5 -106t-196.5 -31q-161 11 -279.5 125t-134.5 274q-12 111 27.5 210.5t118.5 170.5l-71 107q-96 -80 -151 -194t-55 -244q0 -27 -18.5 -46.5t-45.5 -19.5h-256h-69q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5 t-131.5 316.5t131.5 316.5t316.5 131.5q76 0 152 -27l24 45q-123 110 -304 110h-64q-26 0 -45 19t-19 45t19 45t45 19h128q78 0 145 -13.5t116.5 -38.5t71.5 -39.5t51 -36.5h512h115l-85 128h-222q-30 0 -49 22.5t-14 52.5q4 23 23 38t43 15h253q33 0 53 -28l70 -105 l114 114q19 19 46 19h101q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-179l115 -172q131 63 275 36q143 -26 244 -134.5t118 -253.5zM448 128q115 0 203 72.5t111 183.5h-314q-35 0 -55 31q-18 32 -1 63l147 277q-47 13 -91 13q-132 0 -226 -94t-94 -226t94 -226 t226 -94zM1856 128q132 0 226 94t94 226t-94 226t-226 94q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94z" /> +<glyph unicode="" d="M1408 0q0 -63 -61.5 -113.5t-164 -81t-225 -46t-253.5 -15.5t-253.5 15.5t-225 46t-164 81t-61.5 113.5q0 49 33 88.5t91 66.5t118 44.5t131 29.5q26 5 48 -10.5t26 -41.5q5 -26 -10.5 -48t-41.5 -26q-58 -10 -106 -23.5t-76.5 -25.5t-48.5 -23.5t-27.5 -19.5t-8.5 -12 q3 -11 27 -26.5t73 -33t114 -32.5t160.5 -25t201.5 -10t201.5 10t160.5 25t114 33t73 33.5t27 27.5q-1 4 -8.5 11t-27.5 19t-48.5 23.5t-76.5 25t-106 23.5q-26 4 -41.5 26t-10.5 48q4 26 26 41.5t48 10.5q71 -12 131 -29.5t118 -44.5t91 -66.5t33 -88.5zM1024 896v-384 q0 -26 -19 -45t-45 -19h-64v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384h-64q-26 0 -45 19t-19 45v384q0 53 37.5 90.5t90.5 37.5h384q53 0 90.5 -37.5t37.5 -90.5zM928 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5 t158.5 -65.5t65.5 -158.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 512h305q-5 -6 -10 -10.5t-9 -7.5l-3 -4l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-5 2 -21 20h369q22 0 39.5 13.5t22.5 34.5l70 281l190 -667q6 -20 23 -33t39 -13q21 0 38 13t23 33l146 485l56 -112q18 -35 57 -35zM1792 940q0 -145 -103 -300h-369l-111 221 q-8 17 -25.5 27t-36.5 8q-45 -5 -56 -46l-129 -430l-196 686q-6 20 -23.5 33t-39.5 13t-39 -13.5t-22 -34.5l-116 -464h-423q-103 155 -103 300q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124 t127 -344z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292 q11 134 80.5 249t182 188t245.5 88q170 19 319 -54t236 -212t87 -306zM128 960q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5z" /> +<glyph unicode="" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-382 -383q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5 q203 0 359 -126l382 382h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M830 1220q145 -72 233.5 -210.5t88.5 -305.5q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5 t-147.5 384.5q0 167 88.5 305.5t233.5 210.5q-165 96 -228 273q-6 16 3.5 29.5t26.5 13.5h69q21 0 29 -20q44 -106 140 -171t214 -65t214 65t140 171q8 20 37 20h61q17 0 26.5 -13.5t3.5 -29.5q-63 -177 -228 -273zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" d="M1024 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-149 16 -270.5 103t-186.5 223.5t-53 291.5q16 204 160 353.5t347 172.5q118 14 228 -19t198 -103l255 254h-134q-14 0 -23 9t-9 23v64zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5t-147.5 384.5q0 201 126 359l-52 53l-101 -111q-9 -10 -22 -10.5t-23 7.5l-48 44q-10 8 -10.5 21.5t8.5 23.5l105 115l-111 112v-134q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9 t-9 23v288q0 26 19 45t45 19h288q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-133l106 -107l86 94q9 10 22 10.5t23 -7.5l48 -44q10 -8 10.5 -21.5t-8.5 -23.5l-90 -99l57 -56q158 126 359 126t359 -126l255 254h-134q-14 0 -23 9t-9 23v64zM832 256q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1790 1007q12 -155 -52.5 -292t-186 -224t-271.5 -103v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-512v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23 t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292q17 206 164.5 356.5t352.5 169.5q206 21 377 -94q171 115 377 94q205 -19 352.5 -169.5t164.5 -356.5zM896 647q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM576 512q115 0 218 57q-154 165 -154 391 q0 224 154 391q-103 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5zM1152 128v260q-137 15 -256 94q-119 -79 -256 -94v-260h512zM1216 512q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5q-115 0 -218 -57q154 -167 154 -391 q0 -226 -154 -391q103 -57 218 -57z" /> +<glyph unicode="" horiz-adv-x="1920" d="M1536 1120q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-31 -182 -166 -312t-318 -156q-210 -29 -384.5 80t-241.5 300q-117 6 -221 57.5t-177.5 133t-113.5 192.5t-32 230 q9 135 78 252t182 191.5t248 89.5q118 14 227.5 -19t198.5 -103l255 254h-134q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q59 -74 93 -169q182 -9 328 -124l255 254h-134q-14 0 -23 9 t-9 23v64zM1024 704q0 20 -4 58q-162 -25 -271 -150t-109 -292q0 -20 4 -58q162 25 271 150t109 292zM128 704q0 -168 111 -294t276 -149q-3 29 -3 59q0 210 135 369.5t338 196.5q-53 120 -163.5 193t-245.5 73q-185 0 -316.5 -131.5t-131.5 -316.5zM1088 -128 q185 0 316.5 131.5t131.5 316.5q0 168 -111 294t-276 149q3 -29 3 -59q0 -210 -135 -369.5t-338 -196.5q53 -120 163.5 -193t245.5 -73z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1664 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-32 -180 -164.5 -310t-313.5 -157q-223 -34 -409 90q-117 -78 -256 -93v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23 t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-155 17 -279.5 109.5t-187 237.5t-39.5 307q25 187 159.5 322.5t320.5 164.5q224 34 410 -90q146 97 320 97q201 0 359 -126l255 254h-134q-14 0 -23 9 t-9 23v64zM896 391q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM128 704q0 -185 131.5 -316.5t316.5 -131.5q117 0 218 57q-154 167 -154 391t154 391q-101 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5zM1216 256q185 0 316.5 131.5t131.5 316.5 t-131.5 316.5t-316.5 131.5q-117 0 -218 -57q154 -167 154 -391t-154 -391q101 -57 218 -57z" /> +<glyph unicode="" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-213 -214l140 -140q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-140 141l-78 -79q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5 t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5q203 0 359 -126l78 78l-172 172q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l172 -172l213 213h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M640 892q217 -24 364.5 -187.5t147.5 -384.5q0 -167 -87 -306t-236 -212t-319 -54q-133 15 -245.5 88t-182 188t-80.5 249q-12 155 52.5 292t186 224t271.5 103v132h-160q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h160v165l-92 -92q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22 t9 23l202 201q19 19 45 19t45 -19l202 -201q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-92 92v-165h160q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-160v-132zM576 -128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5 t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1901 621q19 -19 19 -45t-19 -45l-294 -294q-9 -10 -22.5 -10t-22.5 10l-45 45q-10 9 -10 22.5t10 22.5l185 185h-294v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-132q-24 -217 -187.5 -364.5t-384.5 -147.5q-167 0 -306 87t-212 236t-54 319q15 133 88 245.5 t188 182t249 80.5q155 12 292 -52.5t224 -186t103 -271.5h132v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h294l-185 185q-10 9 -10 22.5t10 22.5l45 45q9 10 22.5 10t22.5 -10zM576 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5 t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-612q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v612q-217 24 -364.5 187.5t-147.5 384.5q0 117 45.5 223.5t123 184t184 123t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5zM576 512q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1024 576q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1152 576q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123 t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5z" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" d="M1451 1408q35 0 60 -25t25 -60v-1366q0 -35 -25 -60t-60 -25h-391v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-735q-35 0 -60 25t-25 60v1366q0 35 25 60t60 25h1366z" /> +<glyph unicode="" horiz-adv-x="1280" d="M0 939q0 108 37.5 203.5t103.5 166.5t152 123t185 78t202 26q158 0 294 -66.5t221 -193.5t85 -287q0 -96 -19 -188t-60 -177t-100 -149.5t-145 -103t-189 -38.5q-68 0 -135 32t-96 88q-10 -39 -28 -112.5t-23.5 -95t-20.5 -71t-26 -71t-32 -62.5t-46 -77.5t-62 -86.5 l-14 -5l-9 10q-15 157 -15 188q0 92 21.5 206.5t66.5 287.5t52 203q-32 65 -32 169q0 83 52 156t132 73q61 0 95 -40.5t34 -102.5q0 -66 -44 -191t-44 -187q0 -63 45 -104.5t109 -41.5q55 0 102 25t78.5 68t56 95t38 110.5t20 111t6.5 99.5q0 173 -109.5 269.5t-285.5 96.5 q-200 0 -334 -129.5t-134 -328.5q0 -44 12.5 -85t27 -65t27 -45.5t12.5 -30.5q0 -28 -15 -73t-37 -45q-2 0 -17 3q-51 15 -90.5 56t-61 94.5t-32.5 108t-11 106.5z" /> +<glyph unicode="" d="M985 562q13 0 97.5 -44t89.5 -53q2 -5 2 -15q0 -33 -17 -76q-16 -39 -71 -65.5t-102 -26.5q-57 0 -190 62q-98 45 -170 118t-148 185q-72 107 -71 194v8q3 91 74 158q24 22 52 22q6 0 18 -1.5t19 -1.5q19 0 26.5 -6.5t15.5 -27.5q8 -20 33 -88t25 -75q0 -21 -34.5 -57.5 t-34.5 -46.5q0 -7 5 -15q34 -73 102 -137q56 -53 151 -101q12 -7 22 -7q15 0 54 48.5t52 48.5zM782 32q127 0 243.5 50t200.5 134t134 200.5t50 243.5t-50 243.5t-134 200.5t-200.5 134t-243.5 50t-243.5 -50t-200.5 -134t-134 -200.5t-50 -243.5q0 -203 120 -368l-79 -233 l242 77q158 -104 345 -104zM782 1414q153 0 292.5 -60t240.5 -161t161 -240.5t60 -292.5t-60 -292.5t-161 -240.5t-240.5 -161t-292.5 -60q-195 0 -365 94l-417 -134l136 405q-108 178 -108 389q0 153 60 292.5t161 240.5t240.5 161t292.5 60z" /> +<glyph unicode="" horiz-adv-x="1792" d="M128 128h1024v128h-1024v-128zM128 640h1024v128h-1024v-128zM1696 192q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM128 1152h1024v128h-1024v-128zM1696 704q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1696 1216 q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1792 384v-384h-1792v384h1792zM1792 896v-384h-1792v384h1792zM1792 1408v-384h-1792v384h1792z" /> +<glyph unicode="" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1664 512h352q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-352q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5 t-9.5 22.5v352h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v352q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-352zM928 288q0 -52 38 -90t90 -38h256v-238q-68 -50 -171 -50h-874q-121 0 -194 69t-73 190q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q79 -61 154.5 -91.5t164.5 -30.5t164.5 30.5t154.5 91.5q20 17 39 17q132 0 217 -96h-223q-52 0 -90 -38t-38 -90v-192z" /> +<glyph unicode="" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1781 320l249 -249q9 -9 9 -23q0 -13 -9 -22l-136 -136q-9 -9 -22 -9q-14 0 -23 9l-249 249l-249 -249q-9 -9 -23 -9q-13 0 -22 9l-136 136 q-9 9 -9 22q0 14 9 23l249 249l-249 249q-9 9 -9 23q0 13 9 22l136 136q9 9 22 9q14 0 23 -9l249 -249l249 249q9 9 23 9q13 0 22 -9l136 -136q9 -9 9 -22q0 -14 -9 -23zM1283 320l-181 -181q-37 -37 -37 -91q0 -53 37 -90l83 -83q-21 -3 -44 -3h-874q-121 0 -194 69 t-73 190q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q154 -122 319 -122t319 122q20 17 39 17q28 0 57 -6q-28 -27 -41 -50t-13 -56q0 -54 37 -91z" /> +<glyph unicode="" horiz-adv-x="2048" d="M256 512h1728q26 0 45 -19t19 -45v-448h-256v256h-1536v-256h-256v1216q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-704zM832 832q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM2048 576v64q0 159 -112.5 271.5t-271.5 112.5h-704 q-26 0 -45 -19t-19 -45v-384h1152z" /> +<glyph unicode="" d="M1536 1536l-192 -448h192v-192h-274l-55 -128h329v-192h-411l-357 -832l-357 832h-411v192h329l-55 128h-274v192h192l-192 448h256l323 -768h378l323 768h256zM768 320l108 256h-216z" /> +<glyph unicode="" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM768 192q80 0 136 56t56 136t-56 136t-136 56 t-136 -56t-56 -136t56 -136t136 -56zM1344 768v512h-1152v-512h1152z" /> +<glyph unicode="" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM288 224q66 0 113 47t47 113t-47 113t-113 47 t-113 -47t-47 -113t47 -113t113 -47zM704 768v512h-544v-512h544zM1248 224q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM1408 768v512h-576v-512h576z" /> +<glyph unicode="" horiz-adv-x="1792" d="M597 1115v-1173q0 -25 -12.5 -42.5t-36.5 -17.5q-17 0 -33 8l-465 233q-21 10 -35.5 33.5t-14.5 46.5v1140q0 20 10 34t29 14q14 0 44 -15l511 -256q3 -3 3 -5zM661 1014l534 -866l-534 266v600zM1792 996v-1054q0 -25 -14 -40.5t-38 -15.5t-47 13l-441 220zM1789 1116 q0 -3 -256.5 -419.5t-300.5 -487.5l-390 634l324 527q17 28 52 28q14 0 26 -6l541 -270q4 -2 4 -6z" /> +<glyph unicode="" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1408v-1536h-1536v1536h1536z" /> +<glyph unicode="" horiz-adv-x="2296" d="M478 -139q-8 -16 -27 -34.5t-37 -25.5q-25 -9 -51.5 3.5t-28.5 31.5q-1 22 40 55t68 38q23 4 34 -21.5t2 -46.5zM1819 -139q7 -16 26 -34.5t38 -25.5q25 -9 51.5 3.5t27.5 31.5q2 22 -39.5 55t-68.5 38q-22 4 -33 -21.5t-2 -46.5zM1867 -30q13 -27 56.5 -59.5t77.5 -41.5 q45 -13 82 4.5t37 50.5q0 46 -67.5 100.5t-115.5 59.5q-40 5 -63.5 -37.5t-6.5 -76.5zM428 -30q-13 -27 -56 -59.5t-77 -41.5q-45 -13 -82 4.5t-37 50.5q0 46 67.5 100.5t115.5 59.5q40 5 63 -37.5t6 -76.5zM1158 1094h1q-41 0 -76 -15q27 -8 44 -30.5t17 -49.5 q0 -35 -27 -60t-65 -25q-52 0 -80 43q-5 -23 -5 -42q0 -74 56 -126.5t135 -52.5q80 0 136 52.5t56 126.5t-56 126.5t-136 52.5zM1462 1312q-99 109 -220.5 131.5t-245.5 -44.5q27 60 82.5 96.5t118 39.5t121.5 -17t99.5 -74.5t44.5 -131.5zM2212 73q8 -11 -11 -42 q7 -23 7 -40q1 -56 -44.5 -112.5t-109.5 -91.5t-118 -37q-48 -2 -92 21.5t-66 65.5q-687 -25 -1259 0q-23 -41 -66.5 -65t-92.5 -22q-86 3 -179.5 80.5t-92.5 160.5q2 22 7 40q-19 31 -11 42q6 10 31 1q14 22 41 51q-7 29 2 38q11 10 39 -4q29 20 59 34q0 29 13 37 q23 12 51 -16q35 5 61 -2q18 -4 38 -19v73q-11 0 -18 2q-53 10 -97 44.5t-55 87.5q-9 38 0 81q15 62 93 95q2 17 19 35.5t36 23.5t33 -7.5t19 -30.5h13q46 -5 60 -23q3 -3 5 -7q10 1 30.5 3.5t30.5 3.5q-15 11 -30 17q-23 40 -91 43q0 6 1 10q-62 2 -118.5 18.5t-84.5 47.5 q-32 36 -42.5 92t-2.5 112q16 126 90 179q23 16 52 4.5t32 -40.5q0 -1 1.5 -14t2.5 -21t3 -20t5.5 -19t8.5 -10q27 -14 76 -12q48 46 98 74q-40 4 -162 -14l47 46q61 58 163 111q145 73 282 86q-20 8 -41 15.5t-47 14t-42.5 10.5t-47.5 11t-43 10q595 126 904 -139 q98 -84 158 -222q85 -10 121 9h1q5 3 8.5 10t5.5 19t3 19.5t3 21.5l1 14q3 28 32 40t52 -5q73 -52 91 -178q7 -57 -3.5 -113t-42.5 -91q-28 -32 -83.5 -48.5t-115.5 -18.5v-10q-71 -2 -95 -43q-14 -5 -31 -17q11 -1 32 -3.5t30 -3.5q1 4 5 8q16 18 60 23h13q5 18 19 30t33 8 t36 -23t19 -36q79 -32 93 -95q9 -40 1 -81q-12 -53 -56 -88t-97 -44q-10 -2 -17 -2q0 -49 -1 -73q20 15 38 19q26 7 61 2q28 28 51 16q14 -9 14 -37q33 -16 59 -34q27 13 38 4q10 -10 2 -38q28 -30 41 -51q23 8 31 -1zM1937 1025q0 -29 -9 -54q82 -32 112 -132 q4 37 -9.5 98.5t-41.5 90.5q-20 19 -36 17t-16 -20zM1859 925q35 -42 47.5 -108.5t-0.5 -124.5q67 13 97 45q13 14 18 28q-3 64 -31 114.5t-79 66.5q-15 -15 -52 -21zM1822 921q-30 0 -44 1q42 -115 53 -239q21 0 43 3q16 68 1 135t-53 100zM258 839q30 100 112 132 q-9 25 -9 54q0 18 -16.5 20t-35.5 -17q-28 -29 -41.5 -90.5t-9.5 -98.5zM294 737q29 -31 97 -45q-13 58 -0.5 124.5t47.5 108.5v0q-37 6 -52 21q-51 -16 -78.5 -66t-31.5 -115q9 -17 18 -28zM471 683q14 124 73 235q-19 -4 -55 -18l-45 -19v1q-46 -89 -20 -196q25 -3 47 -3z M1434 644q8 -38 16.5 -108.5t11.5 -89.5q3 -18 9.5 -21.5t23.5 4.5q40 20 62 85.5t23 125.5q-24 2 -146 4zM1152 1285q-116 0 -199 -82.5t-83 -198.5q0 -117 83 -199.5t199 -82.5t199 82.5t83 199.5q0 116 -83 198.5t-199 82.5zM1380 646q-106 2 -211 0v1q-1 -27 2.5 -86 t13.5 -66q29 -14 93.5 -14.5t95.5 10.5q9 3 11 39t-0.5 69.5t-4.5 46.5zM1112 447q8 4 9.5 48t-0.5 88t-4 63v1q-212 -3 -214 -3q-4 -20 -7 -62t0 -83t14 -46q34 -15 101 -16t101 10zM718 636q-16 -59 4.5 -118.5t77.5 -84.5q15 -8 24 -5t12 21q3 16 8 90t10 103 q-69 -2 -136 -6zM591 510q3 -23 -34 -36q132 -141 271.5 -240t305.5 -154q172 49 310.5 146t293.5 250q-33 13 -30 34l3 9v1v-1q-17 2 -50 5.5t-48 4.5q-26 -90 -82 -132q-51 -38 -82 1q-5 6 -9 14q-7 13 -17 62q-2 -5 -5 -9t-7.5 -7t-8 -5.5t-9.5 -4l-10 -2.5t-12 -2 l-12 -1.5t-13.5 -1t-13.5 -0.5q-106 -9 -163 11q-4 -17 -10 -26.5t-21 -15t-23 -7t-36 -3.5q-2 0 -3 -0.5t-3 -0.5h-3q-179 -17 -203 40q-2 -63 -56 -54q-47 8 -91 54q-12 13 -20 26q-17 29 -26 65q-58 -6 -87 -10q1 -2 4 -10zM507 -118q3 14 3 30q-17 71 -51 130t-73 70 q-41 12 -101.5 -14.5t-104.5 -80t-39 -107.5q35 -53 100 -93t119 -42q51 -2 94 28t53 79zM510 53q23 -63 27 -119q195 113 392 174q-98 52 -180.5 120t-179.5 165q-6 -4 -29 -13q0 -2 -1 -5t-1 -4q31 -18 22 -37q-12 -23 -56 -34q-10 -13 -29 -24h-1q-2 -83 1 -150 q19 -34 35 -73zM579 -113q532 -21 1145 0q-254 147 -428 196q-76 -35 -156 -57q-8 -3 -16 0q-65 21 -129 49q-208 -60 -416 -188h-1v-1q1 0 1 1zM1763 -67q4 54 28 120q14 38 33 71l-1 -1q3 77 3 153q-15 8 -30 25q-42 9 -56 33q-9 20 22 38q-2 4 -2 9q-16 4 -28 12 q-204 -190 -383 -284q198 -59 414 -176zM2155 -90q5 54 -39 107.5t-104 80t-102 14.5q-38 -11 -72.5 -70.5t-51.5 -129.5q0 -16 3 -30q10 -49 53 -79t94 -28q54 2 119 42t100 93z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1524 -25q0 -68 -48 -116t-116 -48t-116.5 48t-48.5 116t48.5 116.5t116.5 48.5t116 -48.5t48 -116.5zM775 -25q0 -68 -48.5 -116t-116.5 -48t-116 48t-48 116t48 116.5t116 48.5t116.5 -48.5t48.5 -116.5zM0 1469q57 -60 110.5 -104.5t121 -82t136 -63t166 -45.5 t200 -31.5t250 -18.5t304 -9.5t372.5 -2.5q139 0 244.5 -5t181 -16.5t124 -27.5t71 -39.5t24 -51.5t-19.5 -64t-56.5 -76.5t-89.5 -91t-116 -104.5t-139 -119q-185 -157 -286 -247q29 51 76.5 109t94 105.5t94.5 98.5t83 91.5t54 80.5t13 70t-45.5 55.5t-116.5 41t-204 23.5 t-304 5q-168 -2 -314 6t-256 23t-204.5 41t-159.5 51.5t-122.5 62.5t-91.5 66.5t-68 71.5t-50.5 69.5t-40 68t-36.5 59.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M896 1472q-169 0 -323 -66t-265.5 -177.5t-177.5 -265.5t-66 -323t66 -323t177.5 -265.5t265.5 -177.5t323 -66t323 66t265.5 177.5t177.5 265.5t66 323t-66 323t-177.5 265.5t-265.5 177.5t-323 66zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348 t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM496 704q16 0 16 -16v-480q0 -16 -16 -16h-32q-16 0 -16 16v480q0 16 16 16h32zM896 640q53 0 90.5 -37.5t37.5 -90.5q0 -35 -17.5 -64t-46.5 -46v-114q0 -14 -9 -23 t-23 -9h-64q-14 0 -23 9t-9 23v114q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5zM896 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM544 928v-96 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 93 65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5v-96q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 146 -103 249t-249 103t-249 -103t-103 -249zM1408 192v512q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-512 q0 -26 19 -45t45 -19h896q26 0 45 19t19 45z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1920 1024v-768h-1664v768h1664zM2048 448h128v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288zM2304 832v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113 v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160q53 0 90.5 -37.5t37.5 -90.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M256 256v768h1280v-768h-1280zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" /> +<glyph unicode="" horiz-adv-x="2304" d="M256 256v768h896v-768h-896zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" /> +<glyph unicode="" horiz-adv-x="2304" d="M256 256v768h512v-768h-512zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" /> +<glyph unicode="" horiz-adv-x="2304" d="M2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23 v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" /> +<glyph unicode="" horiz-adv-x="1280" d="M1133 493q31 -30 14 -69q-17 -40 -59 -40h-382l201 -476q10 -25 0 -49t-34 -35l-177 -75q-25 -10 -49 0t-35 34l-191 452l-312 -312q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v1504q0 42 40 59q12 5 24 5q27 0 45 -19z" /> +<glyph unicode="" horiz-adv-x="1024" d="M832 1408q-320 0 -320 -224v-416h128v-128h-128v-544q0 -224 320 -224h64v-128h-64q-272 0 -384 146q-112 -146 -384 -146h-64v128h64q320 0 320 224v544h-128v128h128v416q0 224 -320 224h-64v128h64q272 0 384 -146q112 146 384 146h64v-128h-64z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2048 1152h-128v-1024h128v-384h-384v128h-1280v-128h-384v384h128v1024h-128v384h384v-128h1280v128h384v-384zM1792 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 -128v128h-128v-128h128zM1664 0v128h128v1024h-128v128h-1280v-128h-128v-1024h128v-128 h1280zM1920 -128v128h-128v-128h128zM1280 896h384v-768h-896v256h-384v768h896v-256zM512 512h640v512h-640v-512zM1536 256v512h-256v-384h-384v-128h640z" /> +<glyph unicode="" horiz-adv-x="2304" d="M2304 768h-128v-640h128v-384h-384v128h-896v-128h-384v384h128v128h-384v-128h-384v384h128v640h-128v384h384v-128h896v128h384v-384h-128v-128h384v128h384v-384zM2048 1024v-128h128v128h-128zM1408 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 256 v128h-128v-128h128zM1536 384h-128v-128h128v128zM384 384h896v128h128v640h-128v128h-896v-128h-128v-640h128v-128zM896 -128v128h-128v-128h128zM2176 -128v128h-128v-128h128zM2048 128v640h-128v128h-384v-384h128v-384h-384v128h-384v-128h128v-128h896v128h128z" /> +<glyph unicode="" d="M1024 288v-416h-928q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68v-928h-416q-40 0 -68 -28t-28 -68zM1152 256h381q-15 -82 -65 -132l-184 -184q-50 -50 -132 -65v381z" /> +<glyph unicode="" d="M1400 256h-248v-248q29 10 41 22l185 185q12 12 22 41zM1120 384h288v896h-1280v-1280h896v288q0 40 28 68t68 28zM1536 1312v-1024q0 -40 -20 -88t-48 -76l-184 -184q-28 -28 -76 -48t-88 -20h-1024q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68 z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1951 538q0 -26 -15.5 -44.5t-38.5 -23.5q-8 -2 -18 -2h-153v140h153q10 0 18 -2q23 -5 38.5 -23.5t15.5 -44.5zM1933 751q0 -25 -15 -42t-38 -21q-3 -1 -15 -1h-139v129h139q3 0 8.5 -0.5t6.5 -0.5q23 -4 38 -21.5t15 -42.5zM728 587v308h-228v-308q0 -58 -38 -94.5 t-105 -36.5q-108 0 -229 59v-112q53 -15 121 -23t109 -9l42 -1q328 0 328 217zM1442 403v113q-99 -52 -200 -59q-108 -8 -169 41t-61 142t61 142t169 41q101 -7 200 -58v112q-48 12 -100 19.5t-80 9.5l-28 2q-127 6 -218.5 -14t-140.5 -60t-71 -88t-22 -106t22 -106t71 -88 t140.5 -60t218.5 -14q101 4 208 31zM2176 518q0 54 -43 88.5t-109 39.5v3q57 8 89 41.5t32 79.5q0 55 -41 88t-107 36q-3 0 -12 0.5t-14 0.5h-455v-510h491q74 0 121.5 36.5t47.5 96.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90 t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="2304" d="M858 295v693q-106 -41 -172 -135.5t-66 -211.5t66 -211.5t172 -134.5zM1362 641q0 117 -66 211.5t-172 135.5v-694q106 41 172 135.5t66 211.5zM1577 641q0 -159 -78.5 -294t-213.5 -213.5t-294 -78.5q-119 0 -227.5 46.5t-187 125t-125 187t-46.5 227.5q0 159 78.5 294 t213.5 213.5t294 78.5t294 -78.5t213.5 -213.5t78.5 -294zM1960 634q0 139 -55.5 261.5t-147.5 205.5t-213.5 131t-252.5 48h-301q-176 0 -323.5 -81t-235 -230t-87.5 -335q0 -171 87 -317.5t236 -231.5t323 -85h301q129 0 251.5 50.5t214.5 135t147.5 202.5t55.5 246z M2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1664 -96v1088q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5zM1792 992v-1088q0 -66 -47 -113t-113 -47h-1088q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113 zM1408 1376v-160h-128v160q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h160v-128h-160q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1728 1088l-384 -704h768zM448 1088l-384 -704h768zM1269 1280q-14 -40 -45.5 -71.5t-71.5 -45.5v-1291h608q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1344q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h608v1291q-40 14 -71.5 45.5t-45.5 71.5h-491q-14 0 -23 9t-9 23v64 q0 14 9 23t23 9h491q21 57 70 92.5t111 35.5t111 -35.5t70 -92.5h491q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-491zM1088 1264q33 0 56.5 23.5t23.5 56.5t-23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5zM2176 384q0 -73 -46.5 -131t-117.5 -91 t-144.5 -49.5t-139.5 -16.5t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81zM896 384q0 -73 -46.5 -131t-117.5 -91t-144.5 -49.5t-139.5 -16.5 t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81z" /> +<glyph unicode="" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-77 -29 -149 -92.5 t-129.5 -152.5t-92.5 -210t-35 -253h1024q0 132 -35 253t-92.5 210t-129.5 152.5t-149 92.5q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" /> +<glyph unicode="" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -66 9 -128h1006q9 61 9 128zM1280 -128q0 130 -34 249.5t-90.5 208t-126.5 152t-146 94.5h-230q-76 -31 -146 -94.5t-126.5 -152t-90.5 -208t-34 -249.5h1024z" /> +<glyph unicode="" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -206 85 -384h854q85 178 85 384zM1223 192q-54 141 -145.5 241.5t-194.5 142.5h-230q-103 -42 -194.5 -142.5t-145.5 -241.5h910z" /> +<glyph unicode="" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-137 -51 -244 -196 h700q-107 145 -244 196q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" /> +<glyph unicode="" d="M1504 -64q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472zM130 0q3 55 16 107t30 95t46 87t53.5 76t64.5 69.5t66 60t70.5 55t66.5 47.5t65 43q-43 28 -65 43t-66.5 47.5t-70.5 55t-66 60t-64.5 69.5t-53.5 76t-46 87 t-30 95t-16 107h1276q-3 -55 -16 -107t-30 -95t-46 -87t-53.5 -76t-64.5 -69.5t-66 -60t-70.5 -55t-66.5 -47.5t-65 -43q43 -28 65 -43t66.5 -47.5t70.5 -55t66 -60t64.5 -69.5t53.5 -76t46 -87t30 -95t16 -107h-1276zM1504 1536q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9 h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472z" /> +<glyph unicode="" d="M768 1152q-53 0 -90.5 -37.5t-37.5 -90.5v-128h-32v93q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-429l-32 30v172q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-224q0 -47 35 -82l310 -296q39 -39 39 -102q0 -26 19 -45t45 -19h640q26 0 45 19t19 45v25 q0 41 10 77l108 436q10 36 10 77v246q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-32h-32v125q0 40 -25 72.5t-64 40.5q-14 2 -23 2q-46 0 -79 -33t-33 -79v-128h-32v122q0 51 -32.5 89.5t-82.5 43.5q-5 1 -13 1zM768 1280q84 0 149 -50q57 34 123 34q59 0 111 -27 t86 -76q27 7 59 7q100 0 170 -71.5t70 -171.5v-246q0 -51 -13 -108l-109 -436q-6 -24 -6 -71q0 -80 -56 -136t-136 -56h-640q-84 0 -138 58.5t-54 142.5l-308 296q-76 73 -76 175v224q0 99 70.5 169.5t169.5 70.5q11 0 16 -1q6 95 75.5 160t164.5 65q52 0 98 -21 q72 69 174 69z" /> +<glyph unicode="" horiz-adv-x="1792" d="M880 1408q-46 0 -79 -33t-33 -79v-656h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528v-256l-154 205q-38 51 -102 51q-53 0 -90.5 -37.5t-37.5 -90.5q0 -43 26 -77l384 -512q38 -51 102 -51h688q34 0 61 22t34 56l76 405q5 32 5 59v498q0 46 -33 79t-79 33t-79 -33 t-33 -79v-272h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528h-32v656q0 46 -33 79t-79 33zM880 1536q68 0 125.5 -35.5t88.5 -96.5q19 4 42 4q99 0 169.5 -70.5t70.5 -169.5v-17q105 6 180.5 -64t75.5 -175v-498q0 -40 -8 -83l-76 -404q-14 -79 -76.5 -131t-143.5 -52 h-688q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 106 75 181t181 75q78 0 128 -34v434q0 99 70.5 169.5t169.5 70.5q23 0 42 -4q31 61 88.5 96.5t125.5 35.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1073 -128h-177q-163 0 -226 141q-23 49 -23 102v5q-62 30 -98.5 88.5t-36.5 127.5q0 38 5 48h-261q-106 0 -181 75t-75 181t75 181t181 75h113l-44 17q-74 28 -119.5 93.5t-45.5 145.5q0 106 75 181t181 75q46 0 91 -17l628 -239h401q106 0 181 -75t75 -181v-668 q0 -88 -54 -157.5t-140 -90.5l-339 -85q-92 -23 -186 -23zM1024 583l-155 -71l-163 -74q-30 -14 -48 -41.5t-18 -60.5q0 -46 33 -79t79 -33q26 0 46 10l338 154q-49 10 -80.5 50t-31.5 90v55zM1344 272q0 46 -33 79t-79 33q-26 0 -46 -10l-290 -132q-28 -13 -37 -17 t-30.5 -17t-29.5 -23.5t-16 -29t-8 -40.5q0 -50 31.5 -82t81.5 -32q20 0 38 9l352 160q30 14 48 41.5t18 60.5zM1112 1024l-650 248q-24 8 -46 8q-53 0 -90.5 -37.5t-37.5 -90.5q0 -40 22.5 -73t59.5 -47l526 -200v-64h-640q-53 0 -90.5 -37.5t-37.5 -90.5t37.5 -90.5 t90.5 -37.5h535l233 106v198q0 63 46 106l111 102h-69zM1073 0q82 0 155 19l339 85q43 11 70 45.5t27 78.5v668q0 53 -37.5 90.5t-90.5 37.5h-308l-136 -126q-36 -33 -36 -82v-296q0 -46 33 -77t79 -31t79 35t33 81v208h32v-208q0 -70 -57 -114q52 -8 86.5 -48.5t34.5 -93.5 q0 -42 -23 -78t-61 -53l-310 -141h91z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1151 1536q61 0 116 -28t91 -77l572 -781q118 -159 118 -359v-355q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v177l-286 143h-546q-80 0 -136 56t-56 136v32q0 119 84.5 203.5t203.5 84.5h420l42 128h-686q-100 0 -173.5 67.5t-81.5 166.5q-65 79 -65 182v32 q0 80 56 136t136 56h959zM1920 -64v355q0 157 -93 284l-573 781q-39 52 -103 52h-959q-26 0 -45 -19t-19 -45q0 -32 1.5 -49.5t9.5 -40.5t25 -43q10 31 35.5 50t56.5 19h832v-32h-832q-26 0 -45 -19t-19 -45q0 -44 3 -58q8 -44 44 -73t81 -29h640h91q40 0 68 -28t28 -68 q0 -15 -5 -30l-64 -192q-10 -29 -35 -47.5t-56 -18.5h-443q-66 0 -113 -47t-47 -113v-32q0 -26 19 -45t45 -19h561q16 0 29 -7l317 -158q24 -13 38.5 -36t14.5 -50v-197q0 -26 19 -45t45 -19h384q26 0 45 19t19 45z" /> +<glyph unicode="" horiz-adv-x="2048" d="M816 1408q-48 0 -79.5 -34t-31.5 -82q0 -14 3 -28l150 -624h-26l-116 482q-9 38 -39.5 62t-69.5 24q-47 0 -79 -34t-32 -81q0 -11 4 -29q3 -13 39 -161t68 -282t32 -138v-227l-307 230q-34 26 -77 26q-52 0 -89.5 -36.5t-37.5 -88.5q0 -67 56 -110l507 -379 q34 -26 76 -26h694q33 0 59 20.5t34 52.5l100 401q8 30 10 88t9 86l116 478q3 12 3 26q0 46 -33 79t-80 33q-38 0 -69 -25.5t-40 -62.5l-99 -408h-26l132 547q3 14 3 28q0 47 -32 80t-80 33q-38 0 -68.5 -24t-39.5 -62l-145 -602h-127l-164 682q-9 38 -39.5 62t-68.5 24z M1461 -256h-694q-85 0 -153 51l-507 380q-50 38 -78.5 94t-28.5 118q0 105 75 179t180 74q25 0 49.5 -5.5t41.5 -11t41 -20.5t35 -23t38.5 -29.5t37.5 -28.5l-123 512q-7 35 -7 59q0 93 60 162t152 79q14 87 80.5 144.5t155.5 57.5q83 0 148 -51.5t85 -132.5l103 -428 l83 348q20 81 85 132.5t148 51.5q87 0 152.5 -54t82.5 -139q93 -10 155 -78t62 -161q0 -30 -7 -57l-116 -477q-5 -22 -5 -67q0 -51 -13 -108l-101 -401q-19 -75 -79.5 -122.5t-137.5 -47.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 1408q-53 0 -90.5 -37.5t-37.5 -90.5v-512v-384l-151 202q-41 54 -107 54q-52 0 -89 -38t-37 -90q0 -43 26 -77l384 -512q38 -51 102 -51h718q22 0 39.5 13.5t22.5 34.5l92 368q24 96 24 194v217q0 41 -28 71t-68 30t-68 -28t-28 -68h-32v61q0 48 -32 81.5t-80 33.5 q-46 0 -79 -33t-33 -79v-64h-32v90q0 55 -37 94.5t-91 39.5q-53 0 -90.5 -37.5t-37.5 -90.5v-96h-32v570q0 55 -37 94.5t-91 39.5zM640 1536q107 0 181.5 -77.5t74.5 -184.5v-220q22 2 32 2q99 0 173 -69q47 21 99 21q113 0 184 -87q27 7 56 7q94 0 159 -67.5t65 -161.5 v-217q0 -116 -28 -225l-92 -368q-16 -64 -68 -104.5t-118 -40.5h-718q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 105 74.5 180.5t179.5 75.5q71 0 130 -35v547q0 106 75 181t181 75zM768 128v384h-32v-384h32zM1024 128v384h-32v-384h32zM1280 128v384h-32 v-384h32z" /> +<glyph unicode="" d="M1288 889q60 0 107 -23q141 -63 141 -226v-177q0 -94 -23 -186l-85 -339q-21 -86 -90.5 -140t-157.5 -54h-668q-106 0 -181 75t-75 181v401l-239 628q-17 45 -17 91q0 106 75 181t181 75q80 0 145.5 -45.5t93.5 -119.5l17 -44v113q0 106 75 181t181 75t181 -75t75 -181 v-261q27 5 48 5q69 0 127.5 -36.5t88.5 -98.5zM1072 896q-33 0 -60.5 -18t-41.5 -48l-74 -163l-71 -155h55q50 0 90 -31.5t50 -80.5l154 338q10 20 10 46q0 46 -33 79t-79 33zM1293 761q-22 0 -40.5 -8t-29 -16t-23.5 -29.5t-17 -30.5t-17 -37l-132 -290q-10 -20 -10 -46 q0 -46 33 -79t79 -33q33 0 60.5 18t41.5 48l160 352q9 18 9 38q0 50 -32 81.5t-82 31.5zM128 1120q0 -22 8 -46l248 -650v-69l102 111q43 46 106 46h198l106 233v535q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5v-640h-64l-200 526q-14 37 -47 59.5t-73 22.5 q-53 0 -90.5 -37.5t-37.5 -90.5zM1180 -128q44 0 78.5 27t45.5 70l85 339q19 73 19 155v91l-141 -310q-17 -38 -53 -61t-78 -23q-53 0 -93.5 34.5t-48.5 86.5q-44 -57 -114 -57h-208v32h208q46 0 81 33t35 79t-31 79t-77 33h-296q-49 0 -82 -36l-126 -136v-308 q0 -53 37.5 -90.5t90.5 -37.5h668z" /> +<glyph unicode="" horiz-adv-x="1973" d="M857 992v-117q0 -13 -9.5 -22t-22.5 -9h-298v-812q0 -13 -9 -22.5t-22 -9.5h-135q-13 0 -22.5 9t-9.5 23v812h-297q-13 0 -22.5 9t-9.5 22v117q0 14 9 23t23 9h793q13 0 22.5 -9.5t9.5 -22.5zM1895 995l77 -961q1 -13 -8 -24q-10 -10 -23 -10h-134q-12 0 -21 8.5 t-10 20.5l-46 588l-189 -425q-8 -19 -29 -19h-120q-20 0 -29 19l-188 427l-45 -590q-1 -12 -10 -20.5t-21 -8.5h-135q-13 0 -23 10q-9 10 -9 24l78 961q1 12 10 20.5t21 8.5h142q20 0 29 -19l220 -520q10 -24 20 -51q3 7 9.5 24.5t10.5 26.5l221 520q9 19 29 19h141 q13 0 22 -8.5t10 -20.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1042 833q0 88 -60 121q-33 18 -117 18h-123v-281h162q66 0 102 37t36 105zM1094 548l205 -373q8 -17 -1 -31q-8 -16 -27 -16h-152q-20 0 -28 17l-194 365h-155v-350q0 -14 -9 -23t-23 -9h-134q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h294q128 0 190 -24q85 -31 134 -109 t49 -180q0 -92 -42.5 -165.5t-115.5 -109.5q6 -10 9 -16zM896 1376q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM1792 640 q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> +<glyph unicode="" horiz-adv-x="1792" d="M605 303q153 0 257 104q14 18 3 36l-45 82q-6 13 -24 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78 q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-148 0 -246 -96.5t-98 -240.5q0 -146 97 -241.5t247 -95.5zM1235 303q153 0 257 104q14 18 4 36l-45 82q-8 14 -25 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5 t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-147 0 -245.5 -96.5t-98.5 -240.5q0 -146 97 -241.5t247 -95.5zM896 1376 q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71z" /> +<glyph unicode="" horiz-adv-x="2048" d="M736 736l384 -384l-384 -384l-672 672l672 672l168 -168l-96 -96l-72 72l-480 -480l480 -480l193 193l-289 287zM1312 1312l672 -672l-672 -672l-168 168l96 96l72 -72l480 480l-480 480l-193 -193l289 -287l-96 -96l-384 384z" /> +<glyph unicode="" horiz-adv-x="1792" d="M717 182l271 271l-279 279l-88 -88l192 -191l-96 -96l-279 279l279 279l40 -40l87 87l-127 128l-454 -454zM1075 190l454 454l-454 454l-271 -271l279 -279l88 88l-192 191l96 96l279 -279l-279 -279l-40 40l-87 -88zM1792 640q0 -182 -71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> +<glyph unicode="" horiz-adv-x="2304" d="M651 539q0 -39 -27.5 -66.5t-65.5 -27.5q-39 0 -66.5 27.5t-27.5 66.5q0 38 27.5 65.5t66.5 27.5q38 0 65.5 -27.5t27.5 -65.5zM1805 540q0 -39 -27.5 -66.5t-66.5 -27.5t-66.5 27.5t-27.5 66.5t27.5 66t66.5 27t66.5 -27t27.5 -66zM765 539q0 79 -56.5 136t-136.5 57 t-136.5 -56.5t-56.5 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM1918 540q0 80 -56.5 136.5t-136.5 56.5q-79 0 -136 -56.5t-57 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM850 539q0 -116 -81.5 -197.5t-196.5 -81.5q-116 0 -197.5 82t-81.5 197 t82 196.5t197 81.5t196.5 -81.5t81.5 -196.5zM2004 540q0 -115 -81.5 -196.5t-197.5 -81.5q-115 0 -196.5 81.5t-81.5 196.5t81.5 196.5t196.5 81.5q116 0 197.5 -81.5t81.5 -196.5zM1040 537q0 191 -135.5 326.5t-326.5 135.5q-125 0 -231 -62t-168 -168.5t-62 -231.5 t62 -231.5t168 -168.5t231 -62q191 0 326.5 135.5t135.5 326.5zM1708 1110q-254 111 -556 111q-319 0 -573 -110q117 0 223 -45.5t182.5 -122.5t122 -183t45.5 -223q0 115 43.5 219.5t118 180.5t177.5 123t217 50zM2187 537q0 191 -135 326.5t-326 135.5t-326.5 -135.5 t-135.5 -326.5t135.5 -326.5t326.5 -135.5t326 135.5t135 326.5zM1921 1103h383q-44 -51 -75 -114.5t-40 -114.5q110 -151 110 -337q0 -156 -77 -288t-209 -208.5t-287 -76.5q-133 0 -249 56t-196 155q-47 -56 -129 -179q-11 22 -53.5 82.5t-74.5 97.5 q-80 -99 -196.5 -155.5t-249.5 -56.5q-155 0 -287 76.5t-209 208.5t-77 288q0 186 110 337q-9 51 -40 114.5t-75 114.5h365q149 100 355 156.5t432 56.5q224 0 421 -56t348 -157z" /> +<glyph unicode="" horiz-adv-x="1280" d="M640 629q-188 0 -321 133t-133 320q0 188 133 321t321 133t321 -133t133 -321q0 -187 -133 -320t-321 -133zM640 1306q-92 0 -157.5 -65.5t-65.5 -158.5q0 -92 65.5 -157.5t157.5 -65.5t157.5 65.5t65.5 157.5q0 93 -65.5 158.5t-157.5 65.5zM1163 574q13 -27 15 -49.5 t-4.5 -40.5t-26.5 -38.5t-42.5 -37t-61.5 -41.5q-115 -73 -315 -94l73 -72l267 -267q30 -31 30 -74t-30 -73l-12 -13q-31 -30 -74 -30t-74 30q-67 68 -267 268l-267 -268q-31 -30 -74 -30t-73 30l-12 13q-31 30 -31 73t31 74l267 267l72 72q-203 21 -317 94 q-39 25 -61.5 41.5t-42.5 37t-26.5 38.5t-4.5 40.5t15 49.5q10 20 28 35t42 22t56 -2t65 -35q5 -4 15 -11t43 -24.5t69 -30.5t92 -24t113 -11q91 0 174 25.5t120 50.5l38 25q33 26 65 35t56 2t42 -22t28 -35z" /> +<glyph unicode="" d="M927 956q0 -66 -46.5 -112.5t-112.5 -46.5t-112.5 46.5t-46.5 112.5t46.5 112.5t112.5 46.5t112.5 -46.5t46.5 -112.5zM1141 593q-10 20 -28 32t-47.5 9.5t-60.5 -27.5q-10 -8 -29 -20t-81 -32t-127 -20t-124 18t-86 36l-27 18q-31 25 -60.5 27.5t-47.5 -9.5t-28 -32 q-22 -45 -2 -74.5t87 -73.5q83 -53 226 -67l-51 -52q-142 -142 -191 -190q-22 -22 -22 -52.5t22 -52.5l9 -9q22 -22 52.5 -22t52.5 22l191 191q114 -115 191 -191q22 -22 52.5 -22t52.5 22l9 9q22 22 22 52.5t-22 52.5l-191 190l-52 52q141 14 225 67q67 44 87 73.5t-2 74.5 zM1092 956q0 134 -95 229t-229 95t-229 -95t-95 -229t95 -229t229 -95t229 95t95 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" /> +<glyph unicode="" horiz-adv-x="1720" d="M1565 1408q65 0 110 -45.5t45 -110.5v-519q0 -176 -68 -336t-182.5 -275t-274 -182.5t-334.5 -67.5q-176 0 -335.5 67.5t-274.5 182.5t-183 275t-68 336v519q0 64 46 110t110 46h1409zM861 344q47 0 82 33l404 388q37 35 37 85q0 49 -34.5 83.5t-83.5 34.5q-47 0 -82 -33 l-323 -310l-323 310q-35 33 -81 33q-49 0 -83.5 -34.5t-34.5 -83.5q0 -51 36 -85l405 -388q33 -33 81 -33z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1494 -103l-295 695q-25 -49 -158.5 -305.5t-198.5 -389.5q-1 -1 -27.5 -0.5t-26.5 1.5q-82 193 -255.5 587t-259.5 596q-21 50 -66.5 107.5t-103.5 100.5t-102 43q0 5 -0.5 24t-0.5 27h583v-50q-39 -2 -79.5 -16t-66.5 -43t-10 -64q26 -59 216.5 -499t235.5 -540 q31 61 140 266.5t131 247.5q-19 39 -126 281t-136 295q-38 69 -201 71v50l513 -1v-47q-60 -2 -93.5 -25t-12.5 -69q33 -70 87 -189.5t86 -187.5q110 214 173 363q24 55 -10 79.5t-129 26.5q1 7 1 25v24q64 0 170.5 0.5t180 1t92.5 0.5v-49q-62 -2 -119 -33t-90 -81 l-213 -442q13 -33 127.5 -290t121.5 -274l441 1017q-14 38 -49.5 62.5t-65 31.5t-55.5 8v50l460 -4l1 -2l-1 -44q-139 -4 -201 -145q-526 -1216 -559 -1291h-49z" /> +<glyph unicode="" horiz-adv-x="1792" d="M949 643q0 -26 -16.5 -45t-41.5 -19q-26 0 -45 16.5t-19 41.5q0 26 17 45t42 19t44 -16.5t19 -41.5zM964 585l350 581q-9 -8 -67.5 -62.5t-125.5 -116.5t-136.5 -127t-117 -110.5t-50.5 -51.5l-349 -580q7 7 67 62t126 116.5t136 127t117 111t50 50.5zM1611 640 q0 -201 -104 -371q-3 2 -17 11t-26.5 16.5t-16.5 7.5q-13 0 -13 -13q0 -10 59 -44q-74 -112 -184.5 -190.5t-241.5 -110.5l-16 67q-1 10 -15 10q-5 0 -8 -5.5t-2 -9.5l16 -68q-72 -15 -146 -15q-199 0 -372 105q1 2 13 20.5t21.5 33.5t9.5 19q0 13 -13 13q-6 0 -17 -14.5 t-22.5 -34.5t-13.5 -23q-113 75 -192 187.5t-110 244.5l69 15q10 3 10 15q0 5 -5.5 8t-10.5 2l-68 -15q-14 72 -14 139q0 206 109 379q2 -1 18.5 -12t30 -19t17.5 -8q13 0 13 12q0 6 -12.5 15.5t-32.5 21.5l-20 12q77 112 189 189t244 107l15 -67q2 -10 15 -10q5 0 8 5.5 t2 10.5l-15 66q71 13 134 13q204 0 379 -109q-39 -56 -39 -65q0 -13 12 -13q11 0 48 64q111 -75 187.5 -186t107.5 -241l-56 -12q-10 -2 -10 -16q0 -5 5.5 -8t9.5 -2l57 13q14 -72 14 -140zM1696 640q0 163 -63.5 311t-170.5 255t-255 170.5t-311 63.5t-311 -63.5 t-255 -170.5t-170.5 -255t-63.5 -311t63.5 -311t170.5 -255t255 -170.5t311 -63.5t311 63.5t255 170.5t170.5 255t63.5 311zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191 t191 -286t71 -348z" /> +<glyph unicode="" horiz-adv-x="1792" d="M893 1536q240 2 451 -120q232 -134 352 -372l-742 39q-160 9 -294 -74.5t-185 -229.5l-276 424q128 159 311 245.5t383 87.5zM146 1131l337 -663q72 -143 211 -217t293 -45l-230 -451q-212 33 -385 157.5t-272.5 316t-99.5 411.5q0 267 146 491zM1732 962 q58 -150 59.5 -310.5t-48.5 -306t-153 -272t-246 -209.5q-230 -133 -498 -119l405 623q88 131 82.5 290.5t-106.5 277.5zM896 942q125 0 213.5 -88.5t88.5 -213.5t-88.5 -213.5t-213.5 -88.5t-213.5 88.5t-88.5 213.5t88.5 213.5t213.5 88.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M903 -256q-283 0 -504.5 150.5t-329.5 398.5q-58 131 -67 301t26 332.5t111 312t179 242.5l-11 -281q11 14 68 15.5t70 -15.5q42 81 160.5 138t234.5 59q-54 -45 -119.5 -148.5t-58.5 -163.5q25 -8 62.5 -13.5t63 -7.5t68 -4t50.5 -3q15 -5 9.5 -45.5t-30.5 -75.5 q-5 -7 -16.5 -18.5t-56.5 -35.5t-101 -34l15 -189l-139 67q-18 -43 -7.5 -81.5t36 -66.5t65.5 -41.5t81 -6.5q51 9 98 34.5t83.5 45t73.5 17.5q61 -4 89.5 -33t19.5 -65q-1 -2 -2.5 -5.5t-8.5 -12.5t-18 -15.5t-31.5 -10.5t-46.5 -1q-60 -95 -144.5 -135.5t-209.5 -29.5 q74 -61 162.5 -82.5t168.5 -6t154.5 52t128 87.5t80.5 104q43 91 39 192.5t-37.5 188.5t-78.5 125q87 -38 137 -79.5t77 -112.5q15 170 -57.5 343t-209.5 284q265 -77 412 -279.5t151 -517.5q2 -127 -40.5 -255t-123.5 -238t-189 -196t-247.5 -135.5t-288.5 -49.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1493 1308q-165 110 -359 110q-155 0 -293 -73t-240 -200q-75 -93 -119.5 -218t-48.5 -266v-42q4 -141 48.5 -266t119.5 -218q102 -127 240 -200t293 -73q194 0 359 110q-121 -108 -274.5 -168t-322.5 -60q-29 0 -43 1q-175 8 -333 82t-272 193t-181 281t-67 339 q0 182 71 348t191 286t286 191t348 71h3q168 -1 320.5 -60.5t273.5 -167.5zM1792 640q0 -192 -77 -362.5t-213 -296.5q-104 -63 -222 -63q-137 0 -255 84q154 56 253.5 233t99.5 405q0 227 -99 404t-253 234q119 83 254 83q119 0 226 -65q135 -125 210.5 -295t75.5 -361z " /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 599q0 -56 -7 -104h-1151q0 -146 109.5 -244.5t257.5 -98.5q99 0 185.5 46.5t136.5 130.5h423q-56 -159 -170.5 -281t-267.5 -188.5t-321 -66.5q-187 0 -356 83q-228 -116 -394 -116q-237 0 -237 263q0 115 45 275q17 60 109 229q199 360 475 606 q-184 -79 -427 -354q63 274 283.5 449.5t501.5 175.5q30 0 45 -1q255 117 433 117q64 0 116 -13t94.5 -40.5t66.5 -76.5t24 -115q0 -116 -75 -286q101 -182 101 -390zM1722 1239q0 83 -53 132t-137 49q-108 0 -254 -70q121 -47 222.5 -131.5t170.5 -195.5q51 135 51 216z M128 2q0 -86 48.5 -132.5t134.5 -46.5q115 0 266 83q-122 72 -213.5 183t-137.5 245q-98 -205 -98 -332zM632 715h728q-5 142 -113 237t-251 95q-144 0 -251.5 -95t-112.5 -237z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1792 288v960q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1248v-960q0 -66 -47 -113t-113 -47h-736v-128h352q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23 v64q0 14 9 23t23 9h352v128h-736q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" /> +<glyph unicode="" horiz-adv-x="1792" d="M138 1408h197q-70 -64 -126 -149q-36 -56 -59 -115t-30 -125.5t-8.5 -120t10.5 -132t21 -126t28 -136.5q4 -19 6 -28q51 -238 81 -329q57 -171 152 -275h-272q-48 0 -82 34t-34 82v1304q0 48 34 82t82 34zM1346 1408h308q48 0 82 -34t34 -82v-1304q0 -48 -34 -82t-82 -34 h-178q212 210 196 565l-469 -101q-2 -45 -12 -82t-31 -72t-59.5 -59.5t-93.5 -36.5q-123 -26 -199 40q-32 27 -53 61t-51.5 129t-64.5 258q-35 163 -45.5 263t-5.5 139t23 77q20 41 62.5 73t102.5 45q45 12 83.5 6.5t67 -17t54 -35t43 -48t34.5 -56.5l468 100 q-68 175 -180 287z" /> +<glyph unicode="" d="M1401 -11l-6 -6q-113 -114 -259 -175q-154 -64 -317 -64q-165 0 -317 64q-148 63 -259 175q-113 112 -175 258q-42 103 -54 189q-4 28 48 36q51 8 56 -20q1 -1 1 -4q18 -90 46 -159q50 -124 152 -226q98 -98 226 -152q132 -56 276 -56q143 0 276 56q128 55 225 152l6 6 q10 10 25 6q12 -3 33 -22q36 -37 17 -58zM929 604l-66 -66l63 -63q21 -21 -7 -49q-17 -17 -32 -17q-10 0 -19 10l-62 61l-66 -66q-5 -5 -15 -5q-15 0 -31 16l-2 2q-18 15 -18 29q0 7 8 17l66 65l-66 66q-16 16 14 45q18 18 31 18q6 0 13 -5l65 -66l65 65q18 17 48 -13 q27 -27 11 -44zM1400 547q0 -118 -46 -228q-45 -105 -126 -186q-80 -80 -187 -126t-228 -46t-228 46t-187 126q-82 82 -125 186q-15 32 -15 40h-1q-9 27 43 44q50 16 60 -12q37 -99 97 -167h1v339v2q3 136 102 232q105 103 253 103q147 0 251 -103t104 -249 q0 -147 -104.5 -251t-250.5 -104q-58 0 -112 16q-28 11 -13 61q16 51 44 43l14 -3q14 -3 32.5 -6t30.5 -3q104 0 176 71.5t72 174.5q0 101 -72 171q-71 71 -175 71q-107 0 -178 -80q-64 -72 -64 -160v-413q110 -67 242 -67q96 0 185 36.5t156 103.5t103.5 155t36.5 183 q0 198 -141 339q-140 140 -339 140q-200 0 -340 -140q-53 -53 -77 -87l-2 -2q-8 -11 -13 -15.5t-21.5 -9.5t-38.5 3q-21 5 -36.5 16.5t-15.5 26.5v680q0 15 10.5 26.5t27.5 11.5h877q30 0 30 -55t-30 -55h-811v-483h1q40 42 102 84t108 61q109 46 231 46q121 0 228 -46 t187 -126q81 -81 126 -186q46 -112 46 -229zM1369 1128q9 -8 9 -18t-5.5 -18t-16.5 -21q-26 -26 -39 -26q-9 0 -16 7q-106 91 -207 133q-128 56 -276 56q-133 0 -262 -49q-27 -10 -45 37q-9 25 -8 38q3 16 16 20q130 57 299 57q164 0 316 -64q137 -58 235 -152z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1551 60q15 6 26 3t11 -17.5t-15 -33.5q-13 -16 -44 -43.5t-95.5 -68t-141 -74t-188 -58t-229.5 -24.5q-119 0 -238 31t-209 76.5t-172.5 104t-132.5 105t-84 87.5q-8 9 -10 16.5t1 12t8 7t11.5 2t11.5 -4.5q192 -117 300 -166q389 -176 799 -90q190 40 391 135z M1758 175q11 -16 2.5 -69.5t-28.5 -102.5q-34 -83 -85 -124q-17 -14 -26 -9t0 24q21 45 44.5 121.5t6.5 98.5q-5 7 -15.5 11.5t-27 6t-29.5 2.5t-35 0t-31.5 -2t-31 -3t-22.5 -2q-6 -1 -13 -1.5t-11 -1t-8.5 -1t-7 -0.5h-5.5h-4.5t-3 0.5t-2 1.5l-1.5 3q-6 16 47 40t103 30 q46 7 108 1t76 -24zM1364 618q0 -31 13.5 -64t32 -58t37.5 -46t33 -32l13 -11l-227 -224q-40 37 -79 75.5t-58 58.5l-19 20q-11 11 -25 33q-38 -59 -97.5 -102.5t-127.5 -63.5t-140 -23t-137.5 21t-117.5 65.5t-83 113t-31 162.5q0 84 28 154t72 116.5t106.5 83t122.5 57 t130 34.5t119.5 18.5t99.5 6.5v127q0 65 -21 97q-34 53 -121 53q-6 0 -16.5 -1t-40.5 -12t-56 -29.5t-56 -59.5t-48 -96l-294 27q0 60 22 119t67 113t108 95t151.5 65.5t190.5 24.5q100 0 181 -25t129.5 -61.5t81 -83t45 -86t12.5 -73.5v-589zM692 597q0 -86 70 -133 q66 -44 139 -22q84 25 114 123q14 45 14 101v162q-59 -2 -111 -12t-106.5 -33.5t-87 -71t-32.5 -114.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1536 1280q52 0 90 -38t38 -90v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128zM1152 1376v-288q0 -14 9 -23t23 -9 h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 1376v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM1536 -128v1024h-1408v-1024h1408zM896 448h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224 v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1152 416v-64q0 -14 -9 -23t-23 -9h-576q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h576q14 0 23 -9t9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23 t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h128q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1111 151l-46 -46q-9 -9 -22 -9t-23 9l-188 189l-188 -189q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22t9 23l189 188l-189 188q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l188 -188l188 188q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23l-188 -188l188 -188q9 -10 9 -23t-9 -22z M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1303 572l-512 -512q-10 -9 -23 -9t-23 9l-288 288q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l220 -220l444 444q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23 t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" /> +<glyph unicode="" horiz-adv-x="1792" d="M448 1536q26 0 45 -19t19 -45v-891l536 429q17 14 40 14q26 0 45 -19t19 -45v-379l536 429q17 14 40 14q26 0 45 -19t19 -45v-1152q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h384z" /> +<glyph unicode="" horiz-adv-x="1024" d="M512 448q66 0 128 15v-655q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v655q61 -15 128 -15zM512 1536q212 0 362 -150t150 -362t-150 -362t-362 -150t-362 150t-150 362t150 362t362 150zM512 1312q14 0 23 9t9 23t-9 23t-23 9q-146 0 -249 -103t-103 -249 q0 -14 9 -23t23 -9t23 9t9 23q0 119 84.5 203.5t203.5 84.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1745 1239q10 -10 10 -23t-10 -23l-141 -141q-28 -28 -68 -28h-1344q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h576v64q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-64h512q40 0 68 -28zM768 320h256v-512q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v512zM1600 768 q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1344q-40 0 -68 28l-141 141q-10 10 -10 23t10 23l141 141q28 28 68 28h512v192h256v-192h576z" /> +<glyph unicode="" horiz-adv-x="2048" d="M2020 1525q28 -20 28 -53v-1408q0 -20 -11 -36t-29 -23l-640 -256q-24 -11 -48 0l-616 246l-616 -246q-10 -5 -24 -5q-19 0 -36 11q-28 20 -28 53v1408q0 20 11 36t29 23l640 256q24 11 48 0l616 -246l616 246q32 13 60 -6zM736 1390v-1270l576 -230v1270zM128 1173 v-1270l544 217v1270zM1920 107v1270l-544 -217v-1270z" /> +<glyph unicode="" horiz-adv-x="1792" d="M512 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472q0 20 17 28l480 256q7 4 15 4zM1760 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472 q0 20 17 28l480 256q7 4 15 4zM640 1536q8 0 14 -3l512 -256q18 -10 18 -29v-1472q0 -13 -9.5 -22.5t-22.5 -9.5q-8 0 -14 3l-512 256q-18 10 -18 29v1472q0 13 9.5 22.5t22.5 9.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 640q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-110 0 -211 18q-173 -173 -435 -229q-52 -10 -86 -13q-12 -1 -22 6t-13 18q-4 15 20 37q5 5 23.5 21.5t25.5 23.5t23.5 25.5t24 31.5t20.5 37 t20 48t14.5 57.5t12.5 72.5q-146 90 -229.5 216.5t-83.5 269.5q0 174 120 321.5t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5 t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51 t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 130 71 248.5t191 204.5t286 136.5t348 50.5t348 -50.5t286 -136.5t191 -204.5t71 -248.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M512 345l512 295v-591l-512 -296v592zM0 640v-591l512 296zM512 1527v-591l-512 -296v591zM512 936l512 295v-591z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1709 1018q-10 -236 -332 -651q-333 -431 -562 -431q-142 0 -240 263q-44 160 -132 482q-72 262 -157 262q-18 0 -127 -76l-77 98q24 21 108 96.5t130 115.5q156 138 241 146q95 9 153 -55.5t81 -203.5q44 -287 66 -373q55 -249 120 -249q51 0 154 161q101 161 109 246 q13 139 -109 139q-57 0 -121 -26q120 393 459 382q251 -8 236 -326z" /> +<glyph unicode="" d="M0 1408h1536v-1536h-1536v1536zM1085 293l-221 631l221 297h-634l221 -297l-221 -631l317 -304z" /> +<glyph unicode="" d="M0 1408h1536v-1536h-1536v1536zM908 1088l-12 -33l75 -83l-31 -114l25 -25l107 57l107 -57l25 25l-31 114l75 83l-12 33h-95l-53 96h-32l-53 -96h-95zM641 925q32 0 44.5 -16t11.5 -63l174 21q0 55 -17.5 92.5t-50.5 56t-69 25.5t-85 7q-133 0 -199 -57.5t-66 -182.5v-72 h-96v-128h76q20 0 20 -8v-382q0 -14 -5 -20t-18 -7l-73 -7v-88h448v86l-149 14q-6 1 -8.5 1.5t-3.5 2.5t-0.5 4t1 7t0.5 10v387h191l38 128h-231q-6 0 -2 6t4 9v80q0 27 1.5 40.5t7.5 28t19.5 20t36.5 5.5zM1248 96v86l-54 9q-7 1 -9.5 2.5t-2.5 3t1 7.5t1 12v520h-275 l-23 -101l83 -22q23 -7 23 -27v-370q0 -14 -6 -18.5t-20 -6.5l-70 -9v-86h352z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1792 690q0 -58 -29.5 -105.5t-79.5 -72.5q12 -46 12 -96q0 -155 -106.5 -287t-290.5 -208.5t-400 -76.5t-399.5 76.5t-290 208.5t-106.5 287q0 47 11 94q-51 25 -82 73.5t-31 106.5q0 82 58 140.5t141 58.5q85 0 145 -63q218 152 515 162l116 521q3 13 15 21t26 5 l369 -81q18 37 54 59.5t79 22.5q62 0 106 -43.5t44 -105.5t-44 -106t-106 -44t-105.5 43.5t-43.5 105.5l-334 74l-104 -472q300 -9 519 -160q58 61 143 61q83 0 141 -58.5t58 -140.5zM418 491q0 -62 43.5 -106t105.5 -44t106 44t44 106t-44 105.5t-106 43.5q-61 0 -105 -44 t-44 -105zM1228 136q11 11 11 26t-11 26q-10 10 -25 10t-26 -10q-41 -42 -121 -62t-160 -20t-160 20t-121 62q-11 10 -26 10t-25 -10q-11 -10 -11 -25.5t11 -26.5q43 -43 118.5 -68t122.5 -29.5t91 -4.5t91 4.5t122.5 29.5t118.5 68zM1225 341q62 0 105.5 44t43.5 106 q0 61 -44 105t-105 44q-62 0 -106 -43.5t-44 -105.5t44 -106t106 -44z" /> +<glyph unicode="" horiz-adv-x="1792" d="M69 741h1q16 126 58.5 241.5t115 217t167.5 176t223.5 117.5t276.5 43q231 0 414 -105.5t294 -303.5q104 -187 104 -442v-188h-1125q1 -111 53.5 -192.5t136.5 -122.5t189.5 -57t213 -3t208 46.5t173.5 84.5v-377q-92 -55 -229.5 -92t-312.5 -38t-316 53 q-189 73 -311.5 249t-124.5 372q-3 242 111 412t325 268q-48 -60 -78 -125.5t-46 -159.5h635q8 77 -8 140t-47 101.5t-70.5 66.5t-80.5 41t-75 20.5t-56 8.5l-22 1q-135 -5 -259.5 -44.5t-223.5 -104.5t-176 -140.5t-138 -163.5z" /> +<glyph unicode="" horiz-adv-x="2304" d="M0 32v608h2304v-608q0 -66 -47 -113t-113 -47h-1984q-66 0 -113 47t-47 113zM640 256v-128h384v128h-384zM256 256v-128h256v128h-256zM2144 1408q66 0 113 -47t47 -113v-224h-2304v224q0 66 47 113t113 47h1984z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1549 857q55 0 85.5 -28.5t30.5 -83.5t-34 -82t-91 -27h-136v-177h-25v398h170zM1710 267l-4 -11l-5 -10q-113 -230 -330.5 -366t-474.5 -136q-182 0 -348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71q244 0 454.5 -124t329.5 -338l2 -4l8 -16 q-30 -15 -136.5 -68.5t-163.5 -84.5q-6 -3 -479 -268q384 -183 799 -366zM896 -234q250 0 462.5 132.5t322.5 357.5l-287 129q-72 -140 -206 -222t-292 -82q-151 0 -280 75t-204 204t-75 280t75 280t204 204t280 75t280 -73.5t204 -204.5l280 143q-116 208 -321 329 t-443 121q-119 0 -232.5 -31.5t-209 -87.5t-176.5 -137t-137 -176.5t-87.5 -209t-31.5 -232.5t31.5 -232.5t87.5 -209t137 -176.5t176.5 -137t209 -87.5t232.5 -31.5z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1427 827l-614 386l92 151h855zM405 562l-184 116v858l1183 -743zM1424 697l147 -95v-858l-532 335zM1387 718l-500 -802h-855l356 571z" /> +<glyph unicode="" horiz-adv-x="1792" d="M640 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1152 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1664 496v-752h-640v320q0 80 -56 136t-136 56t-136 -56t-56 -136v-320h-640v752q0 16 16 16h96 q16 0 16 -16v-112h128v624q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h16v393q-32 19 -32 55q0 26 19 45t45 19t45 -19t19 -45q0 -36 -32 -55v-9h272q16 0 16 -16v-224q0 -16 -16 -16h-272v-128h16q16 0 16 -16v-112h128 v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-624h128v112q0 16 16 16h96q16 0 16 -16z" /> +<glyph unicode="" horiz-adv-x="2304" d="M2288 731q16 -8 16 -27t-16 -27l-320 -192q-8 -5 -16 -5q-9 0 -16 4q-16 10 -16 28v128h-858q37 -58 83 -165q16 -37 24.5 -55t24 -49t27 -47t27 -34t31.5 -26t33 -8h96v96q0 14 9 23t23 9h320q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v96h-96 q-32 0 -61 10t-51 23.5t-45 40.5t-37 46t-33.5 57t-28.5 57.5t-28 60.5q-23 53 -37 81.5t-36 65t-44.5 53.5t-46.5 17h-360q-22 -84 -91 -138t-157 -54q-106 0 -181 75t-75 181t75 181t181 75q88 0 157 -54t91 -138h104q24 0 46.5 17t44.5 53.5t36 65t37 81.5q19 41 28 60.5 t28.5 57.5t33.5 57t37 46t45 40.5t51 23.5t61 10h107q21 57 70 92.5t111 35.5q80 0 136 -56t56 -136t-56 -136t-136 -56q-62 0 -111 35.5t-70 92.5h-107q-17 0 -33 -8t-31.5 -26t-27 -34t-27 -47t-24 -49t-24.5 -55q-46 -107 -83 -165h1114v128q0 18 16 28t32 -1z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1150 774q0 -56 -39.5 -95t-95.5 -39h-253v269h253q56 0 95.5 -39.5t39.5 -95.5zM1329 774q0 130 -91.5 222t-222.5 92h-433v-896h180v269h253q130 0 222 91.5t92 221.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348 t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" /> +<glyph unicode="" horiz-adv-x="2304" d="M1645 438q0 59 -34 106.5t-87 68.5q-7 -45 -23 -92q-7 -24 -27.5 -38t-44.5 -14q-12 0 -24 3q-31 10 -45 38.5t-4 58.5q23 71 23 143q0 123 -61 227.5t-166 165.5t-228 61q-134 0 -247 -73t-167 -194q108 -28 188 -106q22 -23 22 -55t-22 -54t-54 -22t-55 22 q-75 75 -180 75q-106 0 -181 -74.5t-75 -180.5t75 -180.5t181 -74.5h1046q79 0 134.5 55.5t55.5 133.5zM1798 438q0 -142 -100.5 -242t-242.5 -100h-1046q-169 0 -289 119.5t-120 288.5q0 153 100 267t249 136q62 184 221 298t354 114q235 0 408.5 -158.5t196.5 -389.5 q116 -25 192.5 -118.5t76.5 -214.5zM2048 438q0 -175 -97 -319q-23 -33 -64 -33q-24 0 -43 13q-26 17 -32 48.5t12 57.5q71 104 71 233t-71 233q-18 26 -12 57t32 49t57.5 11.5t49.5 -32.5q97 -142 97 -318zM2304 438q0 -244 -134 -443q-23 -34 -64 -34q-23 0 -42 13 q-26 18 -32.5 49t11.5 57q108 164 108 358q0 195 -108 357q-18 26 -11.5 57.5t32.5 48.5q26 18 57 12t49 -33q134 -198 134 -442z" /> +<glyph unicode="" d="M1500 -13q0 -89 -63 -152.5t-153 -63.5t-153.5 63.5t-63.5 152.5q0 90 63.5 153.5t153.5 63.5t153 -63.5t63 -153.5zM1267 268q-115 -15 -192.5 -102.5t-77.5 -205.5q0 -74 33 -138q-146 -78 -379 -78q-109 0 -201 21t-153.5 54.5t-110.5 76.5t-76 85t-44.5 83 t-23.5 66.5t-6 39.5q0 19 4.5 42.5t18.5 56t36.5 58t64 43.5t94.5 18t94 -17.5t63 -41t35.5 -53t17.5 -49t4 -33.5q0 -34 -23 -81q28 -27 82 -42t93 -17l40 -1q115 0 190 51t75 133q0 26 -9 48.5t-31.5 44.5t-49.5 41t-74 44t-93.5 47.5t-119.5 56.5q-28 13 -43 20 q-116 55 -187 100t-122.5 102t-72 125.5t-20.5 162.5q0 78 20.5 150t66 137.5t112.5 114t166.5 77t221.5 28.5q120 0 220 -26t164.5 -67t109.5 -94t64 -105.5t19 -103.5q0 -46 -15 -82.5t-36.5 -58t-48.5 -36t-49 -19.5t-39 -5h-8h-32t-39 5t-44 14t-41 28t-37 46t-24 70.5 t-10 97.5q-15 16 -59 25.5t-81 10.5l-37 1q-68 0 -117.5 -31t-70.5 -70t-21 -76q0 -24 5 -43t24 -46t53 -51t97 -53.5t150 -58.5q76 -25 138.5 -53.5t109 -55.5t83 -59t60.5 -59.5t41 -62.5t26.5 -62t14.5 -63.5t6 -62t1 -62.5z" /> +<glyph unicode="" d="M704 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1152 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103 t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" /> +<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM864 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192z" /> +<glyph unicode="" d="M1088 352v576q0 14 -9 23t-23 9h-576q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h576q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" /> +<glyph unicode="" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h576q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-576z" /> +<glyph unicode="" horiz-adv-x="1792" d="M1757 128l35 -313q3 -28 -16 -50q-19 -21 -48 -21h-1664q-29 0 -48 21q-19 22 -16 50l35 313h1722zM1664 967l86 -775h-1708l86 775q3 24 21 40.5t43 16.5h256v-128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5v128h384v-128q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5v128h256q25 0 43 -16.5t21 -40.5zM1280 1152v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" /> +<glyph unicode="" horiz-adv-x="2048" d="M1920 768q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-15l-115 -662q-8 -46 -44 -76t-82 -30h-1280q-46 0 -82 30t-44 76l-115 662h-15q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5h1792zM485 -32q26 2 43.5 22.5t15.5 46.5l-32 416q-2 26 -22.5 43.5 t-46.5 15.5t-43.5 -22.5t-15.5 -46.5l32 -416q2 -25 20.5 -42t43.5 -17h5zM896 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1280 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1632 27l32 416 q2 26 -15.5 46.5t-43.5 22.5t-46.5 -15.5t-22.5 -43.5l-32 -416q-2 -26 15.5 -46.5t43.5 -22.5h5q25 0 43.5 17t20.5 42zM476 1244l-93 -412h-132l101 441q19 88 89 143.5t160 55.5h167q0 26 19 45t45 19h384q26 0 45 -19t19 -45h167q90 0 160 -55.5t89 -143.5l101 -441 h-132l-93 412q-11 44 -45.5 72t-79.5 28h-167q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45h-167q-45 0 -79.5 -28t-45.5 -72z" /> +<glyph unicode="" horiz-adv-x="1792" d="M991 512l64 256h-254l-64 -256h254zM1759 1016l-56 -224q-7 -24 -31 -24h-327l-64 -256h311q15 0 25 -12q10 -14 6 -28l-56 -224q-5 -24 -31 -24h-327l-81 -328q-7 -24 -31 -24h-224q-16 0 -26 12q-9 12 -6 28l78 312h-254l-81 -328q-7 -24 -31 -24h-225q-15 0 -25 12 q-9 12 -6 28l78 312h-311q-15 0 -25 12q-9 12 -6 28l56 224q7 24 31 24h327l64 256h-311q-15 0 -25 12q-10 14 -6 28l56 224q5 24 31 24h327l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h254l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h311 q15 0 25 -12q9 -12 6 -28z" /> +<glyph unicode="" d="M841 483l148 -148l-149 -149zM840 1094l149 -149l-148 -148zM710 -130l464 464l-306 306l306 306l-464 464v-611l-255 255l-93 -93l320 -321l-320 -321l93 -93l255 255v-611zM1429 640q0 -209 -32 -365.5t-87.5 -257t-140.5 -162.5t-181.5 -86.5t-219.5 -24.5 t-219.5 24.5t-181.5 86.5t-140.5 162.5t-87.5 257t-32 365.5t32 365.5t87.5 257t140.5 162.5t181.5 86.5t219.5 24.5t219.5 -24.5t181.5 -86.5t140.5 -162.5t87.5 -257t32 -365.5z" /> +<glyph unicode="" horiz-adv-x="1024" d="M596 113l173 172l-173 172v-344zM596 823l173 172l-173 172v-344zM628 640l356 -356l-539 -540v711l-297 -296l-108 108l372 373l-372 373l108 108l297 -296v711l539 -540z" /> +<glyph unicode="" d="M1280 256q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM512 1024q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5 t112.5 -271.5zM1440 1344q0 -20 -13 -38l-1056 -1408q-19 -26 -51 -26h-160q-26 0 -45 19t-19 45q0 20 13 38l1056 1408q19 26 51 26h160q26 0 45 -19t19 -45zM768 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> +<glyph unicode="" horiz-adv-x="1792" /> <glyph unicode="" horiz-adv-x="1792" /> </font> </defs></svg> \ No newline at end of file diff --git a/view/theme/vier/font/fontawesome-webfont.ttf b/view/theme/vier/font/fontawesome-webfont.ttf old mode 100755 new mode 100644 index 96a3639cdde5e8ab459c6380e3b9524ee81641dc..26dea7951a73079223b50653c455c5adf46a4648 GIT binary patch delta 34114 zcmce<2Yggj-Zy^FEmJeSrA}rtnVAsMlbK0?Btz(g5;{^t?+|*`J2XWpDp637vLY&C zLtU|;M%P|gb;T7GjXthrS6!BMeIUsk{@-&afq*>w?EC+`pEt>E=bn4Y@BGfMpOb?> zs?ML%$Q<D~&d9|$fvfM^cjVx?!lEun`4LBYzutZN@=tLh#}N&lJwrznmTb9hjT_H7 zc&?weU`oRSZ^#SqyoKZVbF-!_ZMb+5C2`_9yw}W{zj~%EuW>P+H*usjZ}yBS(+x>) zjN-_}$MHTqd-e=@ib>FoN5c0=$ez7m*{a4au3V1ik~mJMo4;t<lsVhK8_n^$WR8=} z3#P1U;Lq~u9B*O`@GP9NU`A=*&2QkjKgS7M8Wt^G_C{n{PmZ^~&vD6~l{2Q!+>twa zD97iXMSc|y)?M!1%6tFFuJ6?K-_BaXeYm{pNIN9(9ZNwI2uJuEaYp~~gz{DW$Zed& ziQ+b~6wfy%uKZGzdQ0P1e0t+lQsMd${b!Y=b0R?_xCSnYZ;Be9AQQ~z+~=y!4LLXI z+`@A=oZE43f1{C~YCdN^=RFraS9h-dT*JB8x!W2W_<FnP@1~zjKbrn#`m5;&)Ay$D z3}{H>6k*AeVd1Abxf@_*S7imt!X;w+)n>7))RNO-S9?pGc9qYY6$q78@e#qtvXy#E z+@cF__}HM?g9gnO-c>H|dXrm!G$admr3M12ZCq-=Y)<2ky~~~mXHGFDiE1JqOAVB< zZ`%KaCYr@1`-L?<bmpQ!8M5lTWEM+81osAOrX!#Zpw~~nI`UIqA|+0TrSZTRPledL z^2m{uC*E-XrLgy!>ZMx_z4>PRN0ubTj;w5pt~|nz`2LRaa`H#*!JS9Hk3Vu`rO>qU z2rKCPnD@Hp0i`a+RGbv!(zqZO0l>?GUPr01y>}(9NWhn+GCNC4!v03j9qIC&D!S{m zkXAG*JZ;;yX%i}E&%g6vbMwK&?~v;jELeyiVolVWsEA*g8rYOv7WVCUb4T|X)4!sx zeD&c<mTe)wnYAvF3+(|OTs)-Zq=}r5D?~Hwyy(ly4)A7cIJ?y2QllA0Vt31=w4_Ri zctc*N-{EukLcUOGsMI0tdz8NT{+;yftIL+XO3Zf>_j_+Fe{AgsYu0?QcEr|ky)sp_ zo_{JynD)VkAAIm3zvzAX{Bf3%<P!6%%bH$VyzW%nsdWW?#t!kdRadh-AAA59F$Cq* zF^R*hN$2vo9^8|KyBhp*^kP)i1f<t;7A^%1?#h*N-MM}W@&GMDKp(CheLAiLsH5PO zvm@o|Ya$1}Ed@3iYeQv&YUlEmwR3B0=f<}x7coAlEQH7LwQW5nZJIP`lXy1q`kjV@ z4GjnR4VxxR+_-V#giU{#Tg!6N@7Tw+{Bq?I-@!_(trh;kKAjXllz4d?`?P^BNqo8K z$vG$PtCCfm{vs#+3td!mdd|ezI2RCvle`w61&2Q(sY!$=hd|U?67o7EZR@=UrTqun zhI<8*H$Ju(f2vpfmzn&`nWxE_81==U5IpsyIUeI<dHW9E!>8_~kKE2by*7Svt*|yO z*YoRJes|zN0_aaZ*!q%N_5rFK2?qlLK%zDhUsf;>45(GORlAVnauXL2(`IE;RGF6+ zWkNcoB;C>PhUEoD>BVinu0eyxy`lS@wE1aegY*?8rXHr=_4~g5yrnam3L8FAY3M&~ z^%b8V3BUD1l6qoty)n60*^`maB2SV4Nh9*{9%;m(kwJkj#-y(>VSH$u7j)rTh-!AX z<Eg;$r;?tgkA1q2esgm5>XRgWAIbak2WyY7|11{!Z2g#>llpp9bP)e@ugcT(XcNmo zx{>sgtKWKS<%Tcm`7bvV^`9`@5747t3<gGqGXv+cxFOsmG{C7=d4hpRxP(Ch7lnE; z{FB=8&lxETWT^u|o6GJj3726SsR2Vl#U_->VRyQ45=%?6LuJ7#l5OTyqNjb<c|@n! z&up%uKUbN}Dyyn!^{Qf(Y^8rH8yE@=Brk;qmf^0o@AT{qzps9iCHljuXKboIPp$Wr zmGt#zsz~x{uaji8JDbehah5(s@1Ree-Lc~=8ARrjL1)Qgv#F}eWLBw)i&Y<?Fn^$I zoS&6hHoUlR_Ba(u%lDUs28K$CB?o;|J<i{^*k|ZwO3T>s_S-u=DW;0&cds8!>~aD) z2`Xk%=HoDKN&+^(7zH*AG=2sV1{$XV8|4nb0YC!52yh7`?%Lo1WWx%0$W?eI95on1 zKguD4K{89ZCvzn;r3F<5`O$nbJaPSVkFL3yi}!?&lKf0he!i#3@q$I{v1gGxuWJua zs&w|qLuXYfnWVHcE|T3S6=kYaxw)!cnFR&Pm+jYnxjiz6i2n?F@=#0HT**p*v8x7q zdgQuG3@NTPk36y_CD~9+zS-?fA<pB|g5>Gc;R?!%95BZQl0{rzi&rhEy%rM0UBnjg ziX8nQnYgHwx5G{UL<*DX4-hJLxJkbFLHxN)I>k-@?k0tNKQ}qxCKfk$JlKo8Ymz+| zJ*sBu&zu*)wzp$WsLYxjai}<l-OYQd_)r-a3^_%ss`=*cXcIk1o4&jG6;gNd3_UaR z=ubra>F7*)=FCY__X;0-oV~pHI}&~T6j^#U^P7Bn@Y@Z@fQ%cyCH48=WS*tlQ59#q z$f<r-amQDTAa6<6=^16x5+86nDENq_^f$xQh3FdgCEIiNt|ICg{GU4mo41pH(l?9p zCXjM!qbG!0Ix(f=4_-&^w97oF1ugtLz!&8DC}_odF?UQv03qs1vH}q>q;PlQjD(fn zD&llGgLcp<mD=tLW~o(zLY*SEDgtrA2~?B*Zgq~KS$Qb|NsRDPFs0RzENFe!Y^@a7 zxOvuN4KqW@dLoG8(7Ox9)P1pd>$q{#t^5e0r>C76!uL{s20uDWE}FMkSTk}J?d?sq z(fduQUbmyXx#7!lKko|8nY_DiwMrmD_b2DA|6zCzPe{Ec{;SUGlwQgRq}saNcf4>B z`CKRKLD_v=H*N&CklV~1;2!0k<=$fBsY)<1Fe6MHpPKcs1TF8JUKT+2K}(!?RZXh+ z5;jvLTUbeMr!Po}fW#mNr(`#gAbX7@P;Zq(W^W?I|DP6RPn2Z~l_^!L?X2zy0dq*S z(0TRX^FNgOGkhifbiZK9%kx-xz15kTOeU9RXJjK)_P0DZxay#d?@2VOL3Z9slB{N@ zFnTHpbzvz+A+2}+8GX8?_ppkFJ~r}-MjEVF&&`u8wUQ=?d2YZXcahvMxaxpiDP(w( z_}fBK`>;YXdRSN?XAA@NO4b@1FJBlS<zgQ4dRtDmddZ-&^IX}WL1h}8`4#?RPrAR< zyDHaB=IKSpk?a!Xc}Mq<jm}o-Yzs9yfuHg<kyJWw&in!0`zG6yv}WSaX?6GPGHWG% z$tJNuW6+U?^7c;*%dYyQ3%(&{(M%pDMm=OCNoylEpA+@D$d|23TO@YoxZ}<1h}R)s z78o%vKx{eUO(S0RtGycZLL>dO@dpy2Cuo0q0`h{)&wXe`TXe-k53LZIRy;&r;e&0v zMXrq_-T3=2r$5A`E*Il>8b-7RbECNF+&swp%el2Ow}HEf+s56&-3??t#67@0#67}2 z!9CBt$i2+H%AHhDS1o`qs}g5O4PF&H6nu^d1te6KIEN#)GBAXQjd2Bt6JBspFK(Eu zZ*!Bx89nn6ydg?zi43fbSb{1w@w)sdmxLohwJ#zN7m3)sBrKH@uf^pQyb)p|r6IM` zVrL(^ED?)~6cV*15>)wplFOmx{XvVQE+sBoAs?tDLLjK+LxRm`BkF3xniSC!S6IWP z+TRmW>@N%6<P<Y)kS%2vy4aHH$`W6;rwAwQDQWgMNY)2pmMhgtwwc3bvct;W{?X-8 zKW|HE3zE(Bp<C%gWVSW8Z9L)M=Oq<?;(3Fae~_+wk<X>yoAda`h#0lFoo)1!YO>PY zla3;THEj(N8BMo}Ml0DFrSI%}`Ot$R(a1;Lcatt(eks29l1f-<jyIm7f5yn>#Mf>i z-{y`Y{+~Aq#Hsm2($HEGZfiP(AF;A%L(U(0;pqCo62IP&DbjniT01U}X*8}ZtKDw* zCTo(&P%+c4)spE_rX6|6SR#^a6Pc&eBzqnB@g}R1=pGp&m%&I+h?#Bgk*RdQSx6VP zI*H!J3*-$l{uK>Rn%;cV+&WSv^&UEh=;(V<dRsaP(<7n@`H!h2vZ{vky#LG#$23BS z@WgC5MhtrT);`kh$1gPW{J^*Pq~@=>(%0zAq|~&Xesd;|Y-!~I3<qFF<W)qZ#q?S7 z>zDM~wwvi+NZNn>S6?zmCt|#F;`a=Pz{fCGf;hq%7-v#s(o0hVgAO6X3;GAdC_TQB z2V*<FvF(?Q$Hn&_j~3AMf@sftVeH12$jp|SO)tK<Df=mMq@bpzfZ7XsK_P*#b8#c| zwd*+@XXo4;ObSHg1fYvGyR<}dy4XX|$`LE)PdrEgU~8C*sMJp3e)<QRwbK3`segYs znLe=}T^oOG(Zs3-K1>fS<t=1dj*)&&qbp|#e|-7ToAxds1K)Mj^_1qWrWy3*>&CoK zrpWKz(sSJ6_{(%{|8Zmk->tO=na<l+OqjKbR?*Ll_L80>UGI_Fg?DXw40=5p8`E&& ze{)W#NZrv?8w4^YaN$sOCAP4)1cJN=+N?|HNEfPj%*AjdA)=}UFikwV)HC*Oyy4B4 z<*)C!<Lj5-jIERQHWU7bJ$rs2JpIS|ll#^keXV)duIAT{uA8&^@o{fI{P4NRpYFKp zi^mTxS@*&64-P;4wz#TQQ!)OIJH}Ut=jFL`Tko&vDa40t-#(;G=$hx7KR-*@D(>D} z(>9{CWad0b+f3j-tl^~bP=7L^l2&sQIL^O&QH&Jsz9B{|GB}}P3kXNr=?4^5a7fH{ zu*uKe_K9}4a-!e3?K9XtP88|ax|g>6cz<j5{XgC?NB6tMGl%4p+!yX_Yc$^U@L~Rp z!|is&eXIc9#?JAd*qO+WunQfbJj_+@cj0{h{XhPA|FYLgvVx0$*X#IS8{6)zDffN` zf*M4Bs#J`iiXhwuXs!;B?+gfUeqd2PSImXD3a*mt2_a_yi0&|u-RroCkfdjU_%7m> zax1yDoD3o1W^OxoJ15@>5`36D1q}CEw8XERp$FQal)J7zA}+P7(@{-aAaa)vg8bfp zbFlZNj>r``<jbG9LhQBr8i!wz_!W|_g7tt8gBXJ$_|%Y8m~dJ^R2V#lVoq0SqyRj^ z$g8-v^Kp%IPs^M9o&2GeH;4H<eyUA#Oqn>`>?)gPn}*IY%~^$Gb^Eog=MrLHN*qgx z9XFTnTc7bSz4z+$vqP446zuoMx2%8Wne`ivAE&2-J$m;DHcUwsGJ8$Vh?M&xbweXv zavWI$OoCg;*1C<U&ZLY;NcO6@mfPr~WOz?uf7?|0sg(29TlC|lOYi9PcWYLm*O*n1 z#SVkFAS<iDTR5p8tAHI73$qHu53;ZNp$`vTdS%kmq1jiIiSm8NH>{VH_LA$fyJ#h1 za}<|W=jxn!nFWPvqPIJeRZdqoViNR%ir2aF8AoT-U@0clcBrvtE*B}#OjXcIv)L7@ zXvrli*{;WD5HI7*keP+a2b%ZqZx-nO<^u<sNpp4ndHIw4>gs&*SYDJr7tJG&<yVs} z?B#xzvtjXmvApF(UUhYz)SI1u_dA?XB{MhXfFzy9bsPwi#JD|%y+EB%hncv_2oPi1 zDp;VHw7_9nONtH%8il-Ux`)bSJQ(?eP@y0Yz6eXZh<L|`pY9*EXU&q?vzDy7Ys8*E zynkT&C!-`!y2j|}F@yd~-u%@~B<<v?Q~Ph<eB<G9GdHiF?4D(@XIlPzpxeC45{=1` z(*5yCFHx~Vc=p}bcJBKiGGW!`O{*qEj^DlSx!OuG!{#t1hsMqyv+Uy=-y`NRTOW9E z>zK7OCdd44yLE{D!2A9@zr$op>ouVDMSq5g*|1>YV>YZb#Zom!0en~V3sBF1Q3XTL z9aIa}flAB+t}!?=;t-nxsWI3cW6WZixQTz;SU)mk7p_cYuj7q(jx2)Bu2WSmkjx-- z3T2E<1U!#%ydWEI7jZ&hSE_MIT%gR>h!PtiiP@momEja`WEG{ciflWHpS6H~w$p~3 z7dabypVhc)WFZ(K@JNvnVT<8GuC*}InZXnT&~vqdH)!)}d9U9qD8i|qI3cq^4kQH- zR~Sxx#4n;Bksrv!c;B1fr7g5sh$H2NwpaLv-laeDi^w?IOk2oxBxdBzZBc7CYg^RJ z8%fOS6=PmO=Hj#XyW83XF-BrE*7hBLw{k;bGbv{+w6t+{yU1Aztvr_s)JNOZKY{iR z;C6Ed!Cb;6nM55_tFj;y{@YW;1=B?)`OQi0Nc6MMJ91fsK)4zza0muc2kf{OD4S}* za{253txn?bOQ5D+huVh0h>aUD!Wy=Y7=in%PV(hvmg~0}I;FMbx0l6oML)FI-N$0^ z0pK@w%x$-P$f{DZ|C{;}#gQlhlw*{$$u^~RHum8)52aPtd^2J-`*!$nn{9ZM_(`|a zN?kY7HHII0s++DdmA+0tv*IN({Lh8RBg9npNh}6LROXl>@(Do7Ufc?f^V$jtv6%>g z$&yJs3Bd<K7_(ZswakhQ&DAa7MiZ&R_%e+<;ad<Q!BWr|CRQkKK#fY6e!|S1ATmyM zsoE{be6+rv!6H^qIG)#y?mI=DuAb6&w2o&#di)7Vl0k2=x(wL`Hl0DAq&Mhn1=$9d z)uJ~@Ndn8l^W@&0tJ`+1-l@$f7#14$v6Fx0Lrb4*Po8^zkNNIAPj(;6hp(v9J|13u zU9Ool#iGo98;$WLvdC*}Ja5zRqE#!X)uLCEs<Eq+L@~vem6DR>OA*B+wH=9GQLPrV zZHHFha?9$<B{wgelKOcxnq<!@ugI;sE!SUF<<GsXDz~CMXV}oMyz;?Y8KZ!YD-Mb^ z2r^d#sI=3_2Mb_0fU;!-sRE#UGP4I`z5^~1W>A1HhQX6}c}s&J$^aZ5*}TEUnE~K- zg*F8c5)gPVGYi@nF*iGNNZ8B<$OihLf@mOs6KUc}y*E5R>vunylZFoMSHIAoc9HAj zYJ2cmdV#2)9W*F;?eC+qLK%nLI4v)O=a!E8!VObSAUW=}_g!L3o55Z_xpd=gx4wP; zqot3#=<7ba)zVm)x9P+QDMmCWPHgH?`KU?fG&y|MM<<eBX1%m!kXrxE!9{QMn^*VE zhP2>)pToMogs9W<7lIC^m1H<$`EHjxea;daC~j)t;fd90si)(uw=c@{WI{DLLC>E! zfr681o2$&I&miLD4Vw}?my=>rGlaXL97w;{Zsa`$kbL053d2qXW4fqzi_E7|AOgmK zo69Yb#_I>TWVj*PjmT1L?R)>CE&qCNZOyV({ZjKpDKoWOeMJszU`10my!6pa8>SWs z#FSG}-z_y$67y2~ty)$?V}aCYAoU`=U(wV+ZHUA|we=gH+qm($>KT=pnUyoDi-(4N zNhk^h>e7;3W>KH%bKC9i?93$5m@K=}bSMf1Ci%icg`pJPW(QnoD4CU3OCDs!ZDd^i zVoZ&Wg2#c&6R#24m_qFXK`#b8jkv+upzph!1b#g96Sg@$SV0zy1YX9^!Qy#W6=tJT z;z+Ti6NR{ZyS2<3zfCfe#ZI5p_eE8f(~+gxS#ITnv*@jhG(KCte*LQ|UtX59j9xd3 zHdn1*RF|Dyw`hG;GvzszRuJP4TCMyz-kRnhDe)Qh)Kok9OM{O*eEZ%r7CSF>p~Lw{ z?Wt)FTD14}&(C)q5Y5hx4(NKGi7EU=2Pej)HpRCDa~!9(pm7<b@qMFHwiFN^VoD?| zTC>fZ2h+eK#^~wO%|@%$*laaMjn>5Nu79pi(r^80^Ft)}QJ7g2l%f~t4x#A`eHOUV zDXVfzMDwG>y7$I!cbaTF>0hmM2Sa&=hzSoJv|0dJ4k|7>=7VJO3IdUZnMaI#Rsj#h z!jvSSWR(P9O*D=(OHHW(r3Sj;hVgq({q@w|@whC1=N@tcZDp;F&tkQ(w$Td`#+TN! zRHCBm?|El=A~*Z!26BVdScv|$#4h)S(F;ZE7STSaFnt)wusQ%UM+eMYa)5)5904_E z6aj<)=%COBwW_0yLe=fZ07RwYdZn?6+tc*)vD*(<t8A7&4ozOuk4<@+j7p15RUN<Y za+}3h`_sQYz@WOR)2GVqrexCZ%)keTqksJ|bDDkbh7EJ=Y35_C?NglfSs8GE7R|5W z2BP^1v9|-hl|&8rUeO(f0nq9(hy6?j?dXHZ0cf5MSl@81?%es$4M}F=d1uAvW4Vhr z8m;l0F84xeHd+CDoUO(iFYbTq+Wi5J^tJ4LmvnU*Yx+{Fv9-SQSSTZ*VqKXBrSU)L z3L6<%(TuQ<6_9EY1c6T=RRRd@#2b_;6VuoPGVZyu&(i<iXAI#DWizy?x*{FX&RL`k z3uuUk^;6zFcXa!kaTWW?&i(&Cp~6P_t^J`zI-<$br0JG60Jfb5W8S3p7t_jbxS=!9 zWH$czkuc{u6_w&#IakHia>KdF43BwL`^;xhX$MUuEU^h7Re}pDGp##Ohyp_i@Hr>^ zrskl^ER-lx1Q;A>1hFtvkOh`_GY5{R$Py}=B(MMHE@i+Igagv+mGlFCqW4Z7J$mvz z5^QUPywS9D)}B4HmMYAUzi{))l{fRzXW7(!R(T=(eJ}m_QS%kFrBm_k6y_w!w>g$B zb=c^i<oMeg$^4BQ;U4_8;_%na4^$+m0F4pRG<2wmwn64oXi&ui&0im`VB!|Js8TZw z>&w-_4Lbi?SO&pY#i>=<!2&@cm5GU>5G5#-oo1E{Hkd#t#6B?b!c|55aEyX2aFGHg zWdLH(mV(L@bQ9GT=r*X@nMuzb|BxQ)wfz0YI*s<0uWwrM*>wsIbsBxzJ?rV6N+RXN zdq2frTkMO&0x`Z5lF!@(SPD&mAyVkMcS2{x`p(mE^3SoSX<KT>r!T2y9r|MBO@F=9 zl$dwX&bbCo)-DBF(S!CtO3MSv>A^NvhWkTO?O(N%UT9N8c1fhrmyrS}suT-5l=FcA z;{<N@zX2l<rxPvhAU=v0UZFn;bW`N9NWjd3MlPY5S?yHCI)efr^U(kP{g=-z-->=( zjrE-f=H3g}yHbeZx#s6~KKfSsOyq!caiGAn%V+G_Gh_L)f?ScnH|6<Ck_A{jMcdM{ z)(tWJmJVB<C0X}APfQtE>!v7E@Vj=zBa0Z@sd#`iJy6kh3E61$0U%pKxKQ|BcZl{t z2B+MCJt2$Q<+Ph1X+Vv;k^@G9fwF3%sBZrwFFdtPt1STO**4@#o;c*;MEb+0uFV_e z4(9mrHeD)8&~6`kC6gTTa3cNTQwnH*%PaYdqQWaHVD1Brfwz{2^v4GS)^H?riK2uQ z)wH7@9UXO)LNZJcWnK|II5QtRO@fcok009n)mD8H=*dAgt_LS1TJbj2ySEcPnvi&L z;WEb&)+N(t&pbf?y3=Ob`R$E+iS<!)V&o1_NPP2_&GuHJpb3YS?zGD7L!@v{j1SZ= zbxgbg=1ZkFcBbeT64vfcWKsp1m)$Nk!Pp0xFQFND+Xu?k!9vvwY&QC-Hcgwa(>+T+ zRjS-{MI+@-tt5eGb-H}yZjD~<6u$snrk^HWJlo8G(F_*!D9OD9p+w@diE7$@`V9kB zhKHF^60%<hOJuyVO6DQ0b~INJ{FFwbhIY{^u8_+F8BK_K@q1V!`00#EwTn~ncdn`w zgf%9{puocS#zacxxFprVw2e3cMLv_24{y3G$3Ze9oumjp)Gps8F6e?{gJPm%wlaa( z{PB4$r4b(!y(1xz3ucOfy#jM3#H@ePQC~@kkFLHVuFcERq*#gUYkA05a#dg(-i-E@ zpyf(s5(n-8ucf7o3H2hIn=GU)XoTNB$OL|ff{NIWH_Y}3HCzOZ2eD^?3rK@1SRmAH zdieEO`-%CX!0;6h&+2_+hAvz0cIL_@exugZb)4EWyRNeL*m2Rwr0U`{!)K4bNq<Sn zNO3!PQ|ZvIPT}bMmv+o9+e{DEKl9M~!O`+yW7lb2hYu)`bo(+V{Dq8j_nbXq=dh}p zma6IzC8K9ApHukA%QXH*-teNX+VpXPY4|)ufF_i<H6TDYLKmolT^>ewn5)rnh)k@R z=ujjCX%@0B{vg^aT>?|XZ)u4DLw8vL4_hTTn>ymOBCf~E;(YL_#Qcobu<&l`5s9~$ z7Zz4*o_gD${zT}PoUTe%+e{iwNgpYzclqQbovA_o;lb(CAN+y-AU`;L)zEYMLE?$) z?NzJZra#>Mhll9oE%R5s&6kYTN;+@XV5lm8XT!WPn(@7yg27?mqIL|>sWk(lp&~UM zs+6J>Ccks|hrzR@nXC|clK$}as!2DJJD>Q|-6)yG*WpLe>zG}Ig`qJUW>5woU~gnm zfEFAbc4SzKFWp<)ZOb;eB}n@bW3Fa)s66F$<>hrP$(<hXjK8}spq|7LW5$*ET`aX+ z^mN?uEL*6qJnn&b663kc=S%J`V7VF%9D=5M0_L#CX(kXjGRSUEIzjv*aLw!JF!jK! z+^&k?29v-cOh$y{SETAIK1%8hHk*MSv>BQVHhPdfkb31I?io}@xZbmv-A3*3g!|5@ zwAqL5eq#4fyKU2`FA+KrV?`j?k{-C~YZ+xF3XV6G4f?s{mervd%coE7m1C*IkEMQ4 zSv$hT)c>t?q?8-osUr;Q!0vpC*^-@sKakj!Gw_euTiBW5rliKa%0yl^rOT>VPo18K zjQ1`@$hzFq-(S#bw`dJU?S%_kqXD<<=Bkvq<M)352#4#6F7F`&_t<Ue8R@D1OFfQ% z`9BweV&(R7fA0^6{3C?R-CwiDqDm|9hW+j4Cy3*m6{cH8uoQU_sD|GJ*Kl;8oR`rI zFSBhEP6ZYBBG&?wv|^#*qq{!Zwd*5k^j8Ps(bNEsR2n5RUW>?x2Pb>ID|+B7+{XZ8 zFfzPUJakSautF$xc-1iDY8he&y`fTO%GM%0L&Y^U(Vrt^0G&bi;&%oa5TQReHNm3V zi>x8NC^!ET6-T2@v8J{rfn7*b9Q^`|l37JCu(emj7>o}+ED;$=hA^WHS2gr7P~q%S zD+`H(&7RF5kqP;&Pn|NGovqDIv-#9htq{r1D$%1T8q)alo7-X=o{`=*<>Z*&mY&%l z#5O<Q(xil@lJ_xHSk*Fv>lKwNQ~7^t0N<$rT&MeMH|A?4u$7O-;V4pWNi^PcWxM1{ z4Qo~+Xvr(AVe!rnteKD~F5UvdvjRBaK<F=m;uYsqR?x42hG8+nytF)^hBRiL(ZZ|1 z68TXn$rdv$bY$3VMmM=AM0scYuj0J)A{Xt)%PH}Td2|iwZSfhC_zY1T-#RbFr};rw z=n|LN(@hBSpVhXX==i>^U#DamJ{SHXw7t}iU&EWyRrDOpLTDI3jj74Pa<2+|^IC+f zLfu-ps{-6)@u3#r7>mUpc~PagPr8VLSOi!h*BegXHliX{M7#Py+Ix_GWEh!*Sk0B> z3Gy=egnUopJeChgh`&`aLkg@95;IEU29K(Ua?doSfGUv(92R*<X%<}K37#q^m@Uu> zs+GEmhe6f}$=R7GQuG@MGeW|p&eYiUb6G;Ab%ok_9rMsZf@Ay*hI7;wEJI<5@4`-( z-7UDBtO!Je;TLAjY!;ChDXkK!i46ND9+Vhj2aC`{n^~^XD%cj-8}L-vf#J$aRO)cL z!brp!hC`fL-|!84&FWOCvw}_+qDuo3Dn!@uIZ6R&iXm%2CqUtlI6^9y0x36<VaA9A zxP-qGUPK|F4#8FGW`@^*gd-SI`HX0<oA{9q&>}y)25P713TH<G)g<Il>Q$lJs6yjY z1&x9_OO=J2L>g1_okBQ-Yyq@J8I%ZX2>VV}L)<~oFn)b#luPup?Ce7%I7<AiU+Qp( zl^sBbl(M8Sh6<grCz?sV&;+b0A)X9RL$Auz0b2?JX-1?uofU>LS@{ERD#|HrSz;KS zxGmt{B3*oGS>Zrgy3{aKu4`y$p7zgmX^XC3F^oT_v8l=USh#4cGd?W-s%uQ~=;sM9 z*)*yYW13p4PtR~0T<N}4t6i@Sjn!+lrXhS*AYC#P7zJL}MQ1X(dXuWf=^ls1JkZrm z5O`57r4<*I<g6*IoO%1r_S`aWRT2UxLsNT<$|jOpl;QCZy152e!8g+v_2`#uwCHoS zFiPYZr1U@*Kh&tzB#jN})WmL0^`*Ov-qbX`PLpnM(SK=&yT!D0yJv_m#n8)bkc9G5 z(?GW|SMP9|-4|YT57&Ba>1jC`y$vZozo|4LYM(Q9w`J!Sq^1a`H5NfISaRe%l1!Vw zzyJR4?+?$NL)5yA^_f~xqUSV%$bZC(DwRI#MtYaIONGfQ2s)|HD}sL$aoztgaqRU9 zf~CfkT3!khyIRMqwCW^{Sz{CDR)|S%b2`t9<jG_{T$q=nv1q$xkYR$Qcd*Oqv662? zxOB9^CH~>Hsq*0|Di@!rP0G{R2rt-1@pgU&eRNQrM$<F;{`-W82gPKgjR+=Fu2GxG zo0Hys@^!vm8oMet(8nU^Mwf;+Y&HAU+7z3!MiNUMo$kk`_0$@C-hu^^G%Bmpy;x(? z<fr*cy1Hxw<vDZinDgm^{9Zi<sB#vxe6G)QS;`mp<@v(gl$0(-ys&qK)0(N*X`LAv zTAk5u%+MsIW86#wcwL`Rplg-a;@72EC95D3iRe{1f=cAQnb{5HH-yZt43cV2HVQ_* zAYHT;bPEkIXiNr;QMiHr_sCmxHUUlrlhK%-Y$>~;qQU3!@;bhYl!Ssr5k)3z0v1no zPM1($Enl2$0xG2^Y17SCgI1UBc4&mhGEzL#{cAI9;)>kO-3%V1F*?I+(vhX>gx)t6 zPxqu`SVddL+O3(!ZkuyeCiC>3mY(YtiGb#H^MqhvyUnWB$o7prcz)yIWy=pAUcQV5 zfR1S^FuC+X|6b3DW5)vW_L0LS6aQxSl_?sP)qF<=zrkhL{(7YNmAefNEg`&`jLXHm z)EJ5-jY<+w0@2znHod?TOGUL-Q(!P;WCJSkO-6IS75b#myl|+F=X-o!UB0+-aM_kA zqLv4hx4HBN{fJ&!?>QWe#XhGXIMRC{wkcc_@RGrUfI-Q2t0;+@7bkV!7M>SM(y#1m zHWs7tb&CAZOG~5|8r>t4W#R@1;Xe^Uhg9k+sQ{Pk0=4u@{gx?Hw#1iC*-~G>B|ffV z-c2|E0X9TZ@yXUd?Vge^<o27tZt$}+GA2)~?;A)Ox`!TnkbeI83mca9?(Hq?%09y9 z>?{@cxURM|$7PgsE>B^3|6w!c_uMzGblK#Y!|HmJ%9&<?H@nM92X-GBfpP@l8v=%! zF*z2v)fEn*T7)pI#2b&O!g?YnRyc&A0R^%<G@<qt54I8rL9jeS^eGit1PE+i9adHr z2f}+qe1@-4hz&X;><0*$klBg?m_ynh%K{!1i+4y!g-qBl35#9mqx9V`H*NZol#$`2 zjNQc-D2pJl9OClAh$UvT$808R=hpf%6pcQ^H;mj%rkp%U570^Uz{!&zD#>tpDc6(q z^OJ&Hj}>MyCQ%AFOHG@;r0-r;-K(7|X<vO2A7ZAgvSxF%HstGOS;FiCODx@dA))qC zbz};wp8j0VUe>b}LW#SbCr`HRKwtwCV`X*$a3NGOg$?4J0D%JPnWA=;V!_arzz8XV z6q5!zbbUoTDsie~7h-DI`mDzDO{_k36rrbFZD>->`KFpllWNq_nn|Hrkz<l<ya_g8 z56jgiBO5Fm2Pqta2U$Kr$$hYvwWo8V(k?YBVgB`9)doij`Au6P*265%T#Z*WqjMW& zG!hNs_@>&>Rc(mZUuvb&NaFdw(I5eOY^Mgv3Sc_7=>Khlup$c4H~$ty|3?jigxiis zE=*piCW@{8e;zDIlq{4M(=24s{QG7I|IrW;&<{ym3;L0c>LDxnya*0})*A>4O!bAf zYk^1#VP9O3X|c$;bi-RZd-AcABK=GBO<QVGQcg}%Qks?g<PPT0Y$Vs>HF*{9BulQ; zoNJM69IJ#AAju>8k|Q36rPSfG*u6M=f+{b<89gBj&XzLe-WA3hAxfKIl!!*7^lU=| zJsXXZC_+}`Cft}C8i+Y6#hPh-EZ*E4+qpB=%zI+wpmLjlC@NIq=@V=D7-cNPg3s=X z&D3Xs>5YX9;ffW04%|x)L=oYzgnSM^TB3woqL~N}a*%{F40Y7vmDxP^Iy9KMG}g+| z7&Dx}i5|Pi#V#OGg`+ibrATc<E+K~L0+YUoCefG}Yis{X#QXN27JLyyh8SMr26u75 z4D&3;v$@7TK(BOwq70$~Ffu(hsPw566u%c1V<OrC6Z%rWuI!z~*U?APxt}mW`k`od zbe5p6UL2qdFf}ELf>IvRqD_eT!U6cRbBv%<XguaS>vx=swIET#(TW%71P-2464*EF z6CuWWllYc>$g}U2XIv6+XvH1-;c>+A%YHE1+6%w~vl+mm5K|OF3tL}}!DzRjCX5zY zDwmhxt{SGU`keCe94VSpPH`6_*PBY7DH%>Yv_6qqp3_!e-hVg}p;!Xc9p$RPN12Vl z=Rjy#*pI<h>>!Mco3QXk?o7v}Rp&F3o=u|Xh^LhkV&&w`ob+eX>vP(;j=BkVUWXj} z1Rw?R<dh2s@va`3a@t#gx;c#J#Ta-A({htBtUbArSOUUf#ViXO4`J##!yWhCVfA!M zz*@y->u|$M?D2<?5fMR9aG(akN@e6>)s2L)%m{`_u<}4K3mn?gNYZVx`aq^jqr3ZV zoyKKwyM&~zTLrAVXr1%TpkDJ=hqiRdCH?ugr_GqZX3hK=(~4%Nr_0Bu=H*R2F3%7K zjIODO4zCfc60IPA8#p<3#rhqrXq5PEy!2Q}mPpnUv6+<7ce@Sf-e58l;$2*()=YhF zszz3qHYAyNUX@c<zpTD4M<wnE_mKomuYyR8Kqx;z2w$-dhuKdOu#o_rRIUIvVGKJK zM>`Qn8?=Psj$whTi1D#P!XdEzVuVCkf2|^@WEG^uD|oN$6voZnw27a(Y3^Jyejhz| zVCCt36Zc_ctB8?LU-ZmBH_|^nPES7u@5Q4efBnCnSwzNm?k5TIzwh$GUthpqm-xUg z<O7%;&!L3VD-RIkzI}Ai)_>h!e=q&`xuf*6hh`l4QBYmcADe<|+$Dhx%LJsx-yL@} z{`ibeW`W<A#Rr6`vRH@q=wMaMW@v>u(`FE(23u@WO{>EAMTiNQRUPjRDl!2}tY_KU zlf}W8GfyH_2M>0<K`|^Zmew*HlsOIN7ABd5_^C<+#KRsBYaIRn9<@agToS-c_?6*; z4`Ti$9;(2)*v<gVRpt{rj8_SN3JjuHGYH$SC|#YPY65}eE`SmUoJ@Xitj^)<nS|5Q zq{un-In_q$V7KINj;ZUWI>_JHL%h1Cu&}03ylvu+#!ZboCiY!2eYR*FWEE#mU(&ag zyH=_gjYs*WcvOg?$d-R85~XsI^r%$1lPmi5t5ELDD7>(yZOVXUHC}JcvH|*SA3Xh> z+Ur$6_w)zb^w&)7Y|zYyZHXghUhT9d!k8{YtvfNp7sFfHy^cW91jI;{5R$!^wy-*2 zt{Vl17pM#?Pgz)sojQ2%R01kd*8BQP&yoVA_$`BPTiUmc>$~)}!8Vu6#-7BECn-i- z>7u!F=_1;CxzkVrZ^s0<ok*9{;&P7+`H6FzHl2Gy&QN=Eyy`0-6I2qI0vyT5V)g3( z!#;K;UOQHtSy;b(=-_YqC12gK<~B|%{H*Q0c-x?F2W?y2`#<kXO;u$}uMK}cCTC{K z$Ns*7QCUXfFE^eo&$)Vl<o3{#YmQKR>#?8?jVJ2gH2xZGA0><)RS`Zu>)Tn!F^K>B zFvu8N4DiV(o@g^}e=9flt?gGLRljSr`QJ6!$B^nWPN~%1z`sQ(R{ymz>gIX;iFr2z zxBioEBzh6Q8ktIOzrB=U)fGcwf){%LNT4zcWJO6*$VmiY!UdY3*QimuH7;BGB<UbF zEC>Q5LYWLxh19HW#dT%Z<#p|vcU@Usaks2gL2SEL>eS0$MXkmdd%`IinLT=Jv^Kw| zTTV}R_sp-2jvYNaaw)M>8ViN#_zJz~Fivw|*mE&7ic^=-ZzcFYaRX`yBRTW9LSjeH zA~tqrS>OO>jJflz9AxXmKt|aKUt+Cshtft8RJO#$)?T>60#qVTw5Xs)Ir-SG5ve@% zVcmS4l3EfpYVopXiKH3Xv!bT^?5s4;^lgR(s)o9FY~+0U?x5|{^pa}p<g$YPVyre4 zn^@GVra-16+z+#x=0YF+Bv{Q9-jpuc<f-f~zp`Y{^*KE?d`&@YP3d5X#7eTl#p+w9 z|5{o>2PGHQH7p-Vat6$7p0=G#-r(rNrW}J1r>Y0NDCWlEnE{3=ow%mJT+0B(z{6kz zz=W{cQ&~9Co<+%e2^v_-WC29r0|piZnJ13vB#b#Sc<i50<EfcYvnLP@)v|!uDF1j? zSUpHW8cX*)wPk6h%{6WNoZXs1#_Na0>v}B6rm@mpbBB~{pXRb<O0k-PxZJ>-+2X_a z*^AtJrM021S(Gcy%OZDr(#SbwRpQx>4E${cuk@=Fw@<UEw#}i&oUU~K(Akkfp6fk& z?yfBI#I)^VC4+z>z9=qTgfbxTXezf6iA<}iWL~E-1ZOLu+?bbGW5fC)fg>p1DM=+T zfm89;N0=W*!5g(?6x)-kh~Q7a(CLyKaLX|)i!cu*;^6#{`xH|I{E|=@mDMhAQ!0)L zwImiFJ5=qe8X(cuxx04FU7R;?{I0nLaz4KdY+={@(e#lCx9&aU&aNpy4DaybY7%94 z^ow+BSF^dg#BL?^?(Fa5E0Ud|+I&B6j4PTb@A{!&(fr{9$cV0CSO?zSlFdM0QP!s* zK;MpSFAhoW<$VG=xB)Hc8|&uoviyD5-1zjdTOFfZ{2ToS8s&($phmYxS66b8!_6jx zCe`70mPEA&jS<0eUQ-*&sWHA8#jY-MU$5>uvyHQj()Y>Z?+Ml3;4P+~6jb#sc>n#r zd75FN-2N8O&UOS;)WB?jo@v)?HnvNImBimcP`qp$f(iPM4He3OUqCZpC)^2`anKMI zR7F^|7gLitg%v!+Bvxj&3x1*Y+ps8CF3je8tZ1mCvAPEOb^MFEh9fJ<eYx@B(;GEC z8|qX|6XSmfM%#L(1_ZN3SDGnA+nR8%=@0nF#^;nvQC&$U?Nc)ebGq1UAeG73!hm3Q ztGM#wwtpUYlkS6Cq3+nN4RxNCM`AZk8#>EV*KnbUOxq_5hO$7a$5&v_40r;mdB(hM z<vAu(Q-*(1jVCpr-ft*KdrdK5DN|DkD(uNEQ;3(tF1u_#@VCUYbf%(9?f7+Rj>0>G zd8*JawczV?i_Zww87MdnEXF2LY>_T==P-Jf2H~1rmF1$0sIhr06ctqqyHb~FD}#km zQRp9~@_Sib%WBI>YgR7*w6{z%T_dWO2{na*D($q?O+vINP^rCLzE4}pPmT{9)tgEs z6}#r76lWDVBf7QfRTGSxuN$$!K5K-1!-z2(Efd$Nmq~^hw$)OscahN;zm0qyDC%9P zw;Jdt^cV5JzD*ra)~hSsoZ7`hZg($BCtt`(#)6tsKb_6{jK;#6qCot9{-Hq8g;*(B z7ig%ObBA8<Hg{F?oG*FeExIvFXAPqx$%J9E=8w{CndnZVK4(5?;iB5fiFwZ{QNg9$ zcto~NgH4ukh-=Y3#t`tv7m_<Mh6IPe3=T3@pb$Am=sFm~Z^@vISu)xWX2RRurKW6P zf)t)aK>vhfgmNTU#=tO`$H4HsJDs{|0}IO*_e;x|v>|<hdA3{=FG<kFJLmN4zU!|Z z;$eRh7zE{L?CtQNGKC}}zQ3V<>gg^$%5y3iUx<#246O~-^W7ptV|(X)&i8SQQQtJ_ z^s)u?w&<_~QJk~e$NT2)LTDZ&{f<MSjtP;>&y{flWURj3z-Fl{!Tl1Q3}Ka5B6cYg zG!oWlhlPOwPCye|x#&``(3J&qJi{!fvhac7C^)ap!Ghi+9c(3m+eBeD*n$AXW6~!y zY_=DUSI)xPJ(M76dK7MVjdE-q8=pRR*WWF>=GJXYw}5P=Ir=Pr&Koff>=!Ai1EH#U z4BK+0Y7ConH3djMsExMv)SRE$wfc3CsM_e8#+n?MaMSbp=ttSMTl(jQhH3KpzW;tf z->L%oNwN2a+K{l?Rek8#qzUwq(ero75Xkvu`2}-#jUSk|m=R{$Dh-HayASQHE+)h6 zsRcDRnay3T>GTVBpY0~~Ry(#LndMB5ujsR=;0NBtETu3S8U6XSEq@DlB_jq5A5Y&7 z6!a;pV0<up3w<}Fn2Is~;X8N=p8piKlb7PJ-t9{#Hbgs0HkbifOnOjH*cBIL>lc^; zZOTF;mre0S+W=fFR=o~kJ0DvOHz922&jy>-D5Mx{qH!?M>I^p~SxLbQ8pkTT_C!9h zCf#JvX@}st!>;{YrxOg~8LfT0!74N_H(1*~Qa+aRtcK-At4-HdZP4p1N&Fx>2J0Y? z#-B*C=yXQm<s^$Oem*%(?bBjOmW6c#ooIJ~XtB_?TVh24+jj-ZR~T(oI)lw}K=(UE z+AGuuzGL_~I{Qhma^mW3p{zZ;BiG(r)4%LJjaFm3%dUB4FV+h%@sR{#Rimbk8lcLj zAJMa~Enof`F_S!ER_;E%h95%cIo4~X-wixXw_1#53t2$#W#tG>Mr(5BJu@dXW+&@} z@bcH_*{ceqwTa!IF84Jbgk>JqsAIlF!Z7*-1K>&!EiO$KXM|a>f(0;-u!-UQ&$3+w zjsV<9i2ypqZ6sU6{KUIw8}<2c*6BsjWKVPZZG)!P5A^pjoAoBMCS))P=Dg7G{5xKK zMMy$cUB1ThzhrHE?UmqF{RJqo-h^$Ob%oDJCWW)D*5L+`Hwa>q(Uhc~K6ucybfZyE z_@p6rn`q9&?jg7Be2L|P*ZhCUB^F-;)WmU(fn%~n^kTEDZjwAo3<;+dUFx8gM6?3- z)6ybFR89D1M%v=_{J{t7`%@2TewlvBALI|l>+wvQspn<lt*0@*nVCe{3nd#%&SaU| zv$4;<swXjq+KSSu7>iikN)YBxKdN#e{KgdtYNbdpq9s8rFu!smGyXkdqCbpns>jY& zuADJ4?Iz@r*J(HU58^^9_P+WdXN)lD8r^)FtvDI`60)!lWB8<oypB)*f#P05aVmT{ zmRw-CXp@;e`g7PVvjGqr+y{P`*ekd`SO`6yL+hC#r~?~2wwaWa04s>2Fx~+zA(*o5 zmJ&M!B9C&1RoQIC2HXD-QUeKiJ!%91JFwKMBTU!kg$9nebY(L-7PJASoKf>^SOHnw zhnMY5A@W-hEfYaxdPLk184`&MX$vksxEO!p`o(oamn=Tm*5}aDWryzj_K>*Y(6Xh6 z@X+=p{l`n!f3$AhN7uh39HsO_`Z8Vq(cR<6+<B58M$gd=fS`%oAQCyRN-HniL(lKs z{C!>FaQ!Gx-4C1h((`*3YOC_df;-7QUpJFm__Rd*5uQ~ap2R9$yqGABXi{oEM7}+A z=-WdyhulC+>rS<tS|{okEgZk+qvcCKxN8yun~@CQqh3)gl}mR&cMm;(cf+*uwa(Rk zdD`N;iRPZ?cH_nL#SOrfnTWRA18qx-;XpJn=Rx>1PFPE&Pyn{C8{&S9JRH{$>6A!* zM833#k4IQ=QGt*MH*#Z>JWvl7CM5KrTj;MuM^<5fnO)Cp-1y8!l4lSNIdXn*>6-(H z-tA7x9G%(gP5Q6Q(YVbd`u^`M3FhY@k1o3?>Xqy@{TnLA-|r82qea>La-w_w1d96g z#Peh?wr||Hal*v-18Mo$UA!p+9Q~D23~qOCL>If=2KQ)Io?}3Yw@Y?@n%!-*riw;i zs<SjDMNYLE-3sxKDINhe7lv_|1){dE<AgK{OBzCG*h(vy$Pr-VLf2J@({zGu;t}U? zSs~BlRNfr$V1caXDtob0I52V3#EF|osV=*FrY^8<!-}-D%<kDbXO7%!#I2<}ik(ia zv(ov}lEFA@oz9oawvX(U)9*$4%Zo3PBz{fj?bi^ehoUCNf3+t|DVpS*Y^yazO19^B z&kGsLb`-T2uyjzO$QR3up}g)0P)mA|l}5gA>%4;~$0N8AzL00&Hn1uYdy3wuL^@+j zL&a>&_6kdg>mV3xzA5UqDx>GPEq|deKlV6%`P(fMqWsH+K5~b@PcgLq7w8MVg5o~e z<W8>?8{ad20lU<Fd(%zdVwXBz9i7nniI;5hl@#~!(zl5m_Vy_*&7$j=Nr#{@cYzkn zVu&YU)et-qp#X<f7C!7c<#iZ&tkQC^ICLz#2!<p_B326lSb<=;EK~vo0ZG`y%IYGp z4~8T*Qk+`)n4V6d|LRFX!|CDCj`7R#2;VO_tjwHBu9x#OKXuwZ&IlahNsnF*?|jd~ z>g3*&N%T~st!jz)l;JOG@~p96_f+yOAzQ2B=e)D{<Hhldn+YK=3--55mX4MJni4*} zo3DG@ZPP2s-rOMhCe(*7!RCJj1%3bC`{O$!#k@LOMMyDURYLoE<J2Nt2oxI;z{%&y z;`Y{PZ9<4uNVE>#n<bE7cXpzEOyk6wV^Ssh?P>rc3qyc@D#i~ME}Ru)8?!KK$B0;j zjh2ZSHzIBbw|FST9E`BQcCdJez1Ty}j-4-|mI6Fr>eZN_yYoj8kNkUYN@^gh1Yb|m zF28Z>WPu)1uiCV25`W8VAw5MjbRT$TqZtB%ikSNkeB%+4Y%^d&@Qe3n_R;C3-o^#I zH%&C8*zJRxH<@??@|pTozx!A)N$+2DcV@XxFIE^w$sa>^dX9ebu`GW~a!6m2^YNDn zE+SvC*(JX*HNe{A9l`g}ELgW;yefV|y>RWu@jc#ph_{*yx~ztMo?a-kw`mUVcZ-IM z9HI8BO(v}2N4@=foqU8i%}FX-Qc}YnH}Xj}h6UwXL$vmb4M{xfgQ5R-O6_PzqD3m! zB!2y6A&IneO(!-F&BcH)@0dS;kqBpNiBpUS0dNbm-)Y5)LzSO|`CvBN@EEK}xc=;) zzF!qjJxG5kr9Z~V!uT07tUqz?`Sv4v{~M}Ty9v`z-uf;5h1@w-KZBOH96Q!>OvUkQ zZu~i@ePAbf@PPaXogbfb>m3=i(tCl(pA&s#7k!DIiI3Q9<QHuu)hkr^<X8)vHCO}* z^Uzz0!@$D@1AHopU=t`<9U^RoAhwf{FtsbFf=924c?YnB9n3E1CRK3p0cU^<XuQbT zu#GMeg`4PKWZGwbH*4R!X6f}y47ooo3vX1X)RvCYnDi-<Yh1Z$tJP6j*QIpQ0DonL z7J87gOKOiJ*AF~)@XmQ@T{XS($4pN%-F`Cx$q@Nb_kKcuxX6+G^Bc)X(uZ`NNWU`* zmSIcy{6F^4KmsJvFrV60q<f)P*TLn8Vk`~vve+$~S6dAlVe&A2W!KCBGehIwc{C6h z(eEKLZqZ;mkG>{f<UW6R8t6vC4gn<)PS*$k0xBi4=u3DuT~08eh_Z4cz$hA)Ze(NM zNfDcGDti}%tq4*IvPt2zN~aFLvX}liv-UbsyiTs2N$h)HIlP*_cO=937=87d8`(Cj zPYVx{VshV}EwfkLykgHAukTrT)5>`_?UvFP?vy9CY|q`^GEv^SaQ-Tyx#MTj``FV= z_9QFY&NtDcH&l--CsV)qFEX`aM9=H#!)Q1Y#LXDGv+%-);0x)?CcWCr6i^v^E?_7y zw=ut2@I=JwR|!@Q+kde=v*4RV#43}2V2)#vw)jMeRwdp7W_$tJYhC}@9n&5uArF-Q zg`Rxy`6J)l|5LtY!W+c?%s;Eivn0iB;x0ZBojt0kc1GX1Bj@j2{Z60K3$ITZy>$1Q z;{{X4kn{W}(kHjx_Gf-fdC|_-CXPJt%jOZwh<fS49!JRRUx(74z*Q!b<*AXWHOn4) znmj&YYTv>mi*9ONH+tfT{$Jeq9zSr$i!Y*+%J9UnC3Fm4@zTcq$CG78hsWxY?JR`5 z4lkvevl%WZUP@KMOBwf&dZyvDLBoN*R3G=Spd}TYj>ID<8QYN2CcM{%B!u-uSvH*D znG~87&m?Pv6O8V!XoQQC;r_LiG6xw$5aqYM`YzB#nQ5;p2@4Xe0V=k_uK+t1Vk(6( zk^F&MUh7+)jab@bt4-uf-4&DR*`nUP#qUWOE=5m#WTsttd+y-K+CiS&$}FElXSI*& zkw3Vi)Mp_d!TA&&RlY*rJa6CR>ulPe#~+>DyGTlAU$%T-)cZp+ZASjUzQswYHEF$H zJap*IA%QMYLz3QASnQep7z3rq8L?5`YGAq_$a0i>4SbSGBg`0__(cphV#5=?0!lFL zjXgl#XJI*sMJV#+RVWdlK3jr<uw=IVs*x$|_zn43G&v&1E$yqMm=6JUM8a%AIT#5x z_hq;TyD$)sai7E^v_}T7_K~!+1#1j)%*29U0kn}dLS|L4uUUcYEoxt_TD)`5p~H9G zv1oBsZjxA5DiLdX$;_#-O}E{;IW|S5GbK5)siUUak!~^Rv{f~d&Sd7TnwlE3+iFm$ zd-k-t)5seI`NQfyKKF6m@UAAJEi2Ladk|sToI4ud|6pggaJtD1Ezh4M&%UjHzuEKp zMHg<Wf8xdg+qb>(=C)8WufbNYj!cI|Sm1WIogz8w3Kpzc{c+u}{DMraUY}~vs^`v# z-LZLnniT}ku;IvkcipU4Evbw~s~Z}2&KjMr(WDb$T%UC_r-sYRBWSWH*!=u(rOCR= z8p+I?jA~ua8nc{Xm1=rg+-U<>ESxi}Zo-7R;j=xOG>bWXT5mFf-%>y8<j%(T&H9p3 zjUcFQoi(Fxzq<Zo>9k%0ZhU<5>)W?&3uW<otxhAkO#Fi;*Fu_+JJMD#p>Eim2J*hf zZZ@Q<$LE$8>k3m0X0amLjp0oOoG4!bZ_*&%%FCFE%pZ1QIUFcVfq)@Xw%|o<d)N<V zfnT;A3?zAM*JTuPd9Vm&?E>2n4!bZZ0U`*ZS9V2_;4WapiRt23mJYfF2kd6B97N!! zG72h_$!RiFt-t%r6)S&!>hSa|jV!8k2B~R2*-ZAlNbb^G>{+E2tJYCyfeRuv&(@Ww zj2bO`7Xo%9nOjn@j&4fx2aJaQ${Av_>5YMvTW;GpyQ+Nb^{ci`D{*9vQ8{{)^{~=U z^Tyq9Z07WPr}RvT*Y~X%Fw$-8K6^pW9x7Fa%^VrltGH_X@`+uvCasL<<>keXkIwnf zJg;~}7o*OWf45Vu6|f1A2)Pac!@RjjrA{J8GHXh5_4;Q2V7p!K>^>$(RW#z(@guiQ z9MC0Q%ga4IA>Qd99!cx5V*c>rk^vKkX2ti9F3fYLOwBL9m$w(fh?@}Y6e}*<oSj8k z&BHJ-I@9>xpFFvK7z<fcET#;MO<5}Gs%JzeS(x2h*~TP8!Zy)mgBndAIl%lb6S{b( zdok7$6S!tZ0-Kc_oo*#=P}${3l6po(>)X$X4J;A?qMhzrtZcy_AHBMy<|0<X72RWN z4Otgi5Lvs+5mxCii%uYjf7-n;MzR`D{wd2W#t?kb#J=Z2q}#a0fBY$IAO~@u=u!N` z#+;AJu&WGn{6Q^4sqFtO0B}Is#_xLi#4F14iDTG`@DnEzE1q9?A+ZL!<N3r1ZJUQx zk+mDkzOV6CSx?S#(`Ht$?Q*?#$l^KJT4F-u<29};D}@k5IbEubHYaNPoFuAmt!Gsw z^F1hsjafa3CgG)$E8Yzilp^}G!_=6OKm-eu%E5QqFCeuY+>pSj!zWu@9p^ym1^93R zsVXduYuij7F(R5cacNDY<D9VfD4UljVQ;r=R!8KL+0x0&?JLmzEWRdT)?@TM%yTtW z$}+sl+sT6@54N<i^dp{Qqq6g)HQjoIG@0UyS?&C-CNY|L4@2dHgYfd?PL>b<B}3r+ z7`4w7O5;~HFjZ^>mwM!dhe9#=Vy8m3=}kw&+8HnM!3@c^0Vrj`m8uVPy6x#XElo<b z(Zni2E+@V11OWulSs}{av^3?UZ&z~i5QoJ-V_>$Y!{5az`zT>grS#U%DIO5;-4u|O z{gsfPWol|tgfN78O0A%ciI7NyuqxhDq6+#Y@GB*JH4HV_CHcdWMY8zuOK??xlkT*} zW68<!yW)50j5Z4%c|7v7`PrV<WIozj4^wD!yjl8}-P;ngd(>DvChLFwRnps`8{?U9 zdORBTwCcVUe>Jq~PK&>`#Km7vx56%V*=}dS6bQBB3R|krOk$7;2{waeExj_O!#fF2 znj%v1oWW+H9<>G+(v0;y-(T_E`k+R|b5@%{qryHWHWh@gmZ)6=E07H|+O7dSf14s) z<aD+c2weifjRYACq(FhB2jVPSP+)?bRgScFTb;6DB`kHg%8JPj=0-)hl^Cyq98jf( z_?n2n<74c9Zp<a`TzNT5r~k5W#v`<)EGx+_h{=-Q=ryGpP14Q>&XRku((WE!`GEcZ zj!S=}fZj(>AGJPGuEWkDdXrP~_B8rai;8*$$Hi}blmw3+Wi40ce5aNdFe=tz0utLz zK^<4Vv3Q@8C-%kx`_eM-A0RxUn_RQaKQzwW7YSeb?+rWn-*;+RqKPE7^~akV-Wga@ zKYm#C{D4Zc^+#g;k#2po@#TFP@`j%to-yO$pA^7qMNax};DibxyapCihaF@sB!wtf z9`Y^RYs#73V-Ln){22X2SQ$S?a>V`Yo*zn2vs849aKkS)sUDG{3UyEsSW1O}0K<YB z|Ivc+ZKul$dw~r3F%m|qRdT8x(a{&_lYiMUHE(SHQPu^6lOHU&XVm0nt~_U?XX&gN z8pE23=qfUzRcQH<PNBny`XH$xszFm7yK`>QXxDG0UyZ)-yWfr4no4fgYZ%O#)|N23 z2`nm^#mExD<^@V&msU=!Jaa~l-+1N>sgz<Z968LdBwgsA<2Tdy*~d(C>iF2oMG@?p zC;(<{R|ID5NYIBZYY`Hpj<9IX_Pr9=Y86D_A{-fVJqvEYb_!W6m<A!9NCLMC;U7If zfKFt?e-3Hkgl$Q+nJM$;rDWD7O%8cNbeAWEJnF0IU%Yu!{UE!}Fn~1Osgkgy=%v7S zDvgko#;*vgC7%A|8sSPZ_GPUXo4g~Iq*PR-EE(aguRr7|bVO?X#--Qyk7?)zV-iu1 z9c{!qGEpayjj<%5E-fuX-}Z;mGUQAlNyTesIMsH#!>Hx8qm`adSL_nZP(BQ1a%LoF zTdDADosD9=R$QH~upgc_7gk7uxiAzC`30<q!~?X&cG?fiC3YBX7yBWQW4$5|Gg^Q# zgn*CDR5jPVB<a-u*VwgyHFaeF%$*P*JPZLth=}3+5CVkclAy+vs(=qrM2HB8R4Fe3 z6|MC_s9Llxr51r8rIxbRs%x$5x@xOcYW;lGwXRy*wbWv3)mrLWi!UUX|2a27ZFj%@ z{(;QQotfX<xpPnEapuf9+y-tc_vV)_afYhtGe%z~;Z;=bP8LX$M;c6~6<OS#A`<yc z!i?!veH~dVOeVt^6NPz`*v}=ry}do$#ZT<wGSZS~%CpE;WSPboOw68y**+>YFuOMa z`}wWtXrDO=>5Ip<h#6-$37r-k1*K#e%oDjVXUmwy+ztLNCNAH_&qXYb2n&l4i^U^c zMyj2i)erG*S!m;n2*wXEqJuE>#NhhQK#_ru+ra*QzzvD_5V^uE7C47e;EFi<vnpXU z%udG>$&5D-6ViBtlvflzev1@uyYSVilpO9CuEYHLC-)e7?Ly3G3A5ZuX-G>SCzGvx zbi9@MvHHg6O_>*t9~?MT@#l1<;`umkZTv)rO(FTm?vjjx(W~>OR8P?<K&75T+A7N+ z!SeFiHcisQ1OlKnpfem9ks?7pL<@r;)E3{KvjEQUm|;1KGVUnHFM@g61Bg^I`PO!r zxRC%9Z5P54x4Szr4{pGZ7NTA7^v*C^jN?ZOT*$&93D|BB3H+a!hEfYh0GG#61lr=& zg7{!$j^Oz$=*M7v8w3-1p_iXf<Q6~1JWjvPhfESWEa0Ya1N#QKbNhP9doM9geqZWE zTqtZ-E$AcL%ekw`T<>NJ$2B*Dig?g=G>OQoxWd~n{cxnCa_rd3jw3(Nnfh|}ucY3( z$3=XdyYsYPe0WJH8B#ieau4=fGl=umSL(L#g_sG`ym9p#=kD_G+k8oUQz~JK(G5?E zDlY7Yy5s#xs0RqLdw>R8;;_-$nnSbt{(eCCj@L$O56R(N`?r(1pg(k#rT<D5&9?gF z&+gw!(in696$a!uZLJ4A3coK06HOS4Mw&?gNaVomJeaqgVGBFJTeFFnfe&fJ2^=}G z`PyfE58Oh9(b>xpm>qx&go7cN_t`wq9oep#8cDtc{$X><?ptd>34qy(@lU@JB`CI3 z5{X2@-H|*K4?sz`<mtavLX0Tg!*w!{uac<*$Mz+!c?RJ$D9F<pveyhoORO6zCD5`k zGez<NCP@ig$jqRUf}j43A^sqE<1h+kCvZNG{Ug%t7+^K29AKFk2)ru$z8f$NfFTfX z5RHO0CExQvLMT!dEGv<=y`7*^T&I(hv!kScYp_b<F~WO<(jy-@JNy!W>vWgqD-$AB zzUEM=r?+opl$>jktv8`8gK532NFEjG>+LBG^;JbCC`*SH81PXI1w%_clu(d|L=`N| zR!W#}oI;#B9fV?OO+smKkU<$F$;T-wmJZU-Uc=as$)Vv|eM&$^8AxT6Wdx+?wc(+Y zL)c+n`Xp%lB)!BOq%;HtmnPJ}VQOrdrxP+q&;{=gG57-m0RaK%Lj_g`ANu_+s<1ju zXSM1Gvwt<0w%59fS^Hl$OsSzVmAG*ai0}ZY(sCokcC$~Gb(zG~j6>bO;H8AZDkuSX zymR2yfJhHAYhY@MNB>bNJM#R1#RC02uElvVK9EtdFC|c?0Ca`|;QV?6o5v25gof~| z{vgSdC|t?&!@p?$IIW|^F<kCyb{*j>_Fi^=dF}UX*;HAi(R)%cz6zl%tzy%u`p37n z-#OW2AcoI=CbPV&HucT%4wu7;E@@Jd)KxQ9I)?~qy~Djd6>ie9GO3$FVUUu2qh=3{ zikACQvEDB!8BESKyzHCmZH$ynYP#4|ky?N9-`h7`lO4FmeRjj=p!wwwH~71Gj8zm7 zrbuB3a#Pfg<K!m-+@uD@mZP6_D8_n7U5CMJA_zERU3~Xf4ih?lz{6i)D6cvNo>}F9 zM+iWFFfYP50@vIc&~$+Nm6!lbRY*aVM}ZMUCFBpmz>W%-+X23xpOZsZ^<J}iZ}m@$ z-tuqwY5sxb1)zx4**9u>NqXckCvV@f(6B|#(kM1BH^p~IRsD<M;j?N_EL(c=mFy6| zk!+*~Bl1*ef>gd<LrlTJ<6ejmhm5Pvn!GYMB__aA%(%@on`dU5%}0iOxMZR+HRAcq zjPy8n&*<mkg3N)@qe9&SqKEl0q%d6(6&a$A3UG6b&tCXiUh0O`bM%_@lHxd3%E;kj zu{4CuVkMr07*d1fA#BX3;XZoS7(anciL+6!ZOUXj<SY2&ncHCK*Wg$E<ZvKP(Un1F z+t9#tN`du=za0WD`TbSRACAA0d|cT-yUMqR$$;%puI-fU*|X2g<7~1Cz`h4zktb({ z1xkaNNL0X<AQBl`CK%KvWAA`|cg;lq{^T#&Ov3sa8v3}e!6mn`Li~^4#(Us{4u7oq z<6RAXPf)4Iq1f2#=>=vio|&!=fra9KG@h-oY;7epKKtmT^MADt8Q56=y$^3wD)c{| z{mG-pKjn_|eeeH#)N*`M+Y9)AJRkp_%+MNe_h))I7vM01^l>msJ^@j{3Jt6r`NM<~ z1X*pOz)yTIcLlG5uOTmbZ4ZMQC(*Zo)YEhLqnhEsoevb{W<JWuq(MG#M7d-V!Brj+ z_ylpQL%=cs^uaK=p$?`R?u4&w=unw2(bJ^<N7Pa7&r1f^*j)d2Q9Uv)?y(md48L>r zssMib_Vb!UVFnZp%!d>_eFT##?4}2$zEP(cjAAf2g;EuP{V}jx6lX@-w^2S=?R+-u z3EN{LM=;n0!x~FKUKNGc?4uxzZZ>Jxktn8uJR9tV`DzYX<dm#>EKnsoEgFLIbYz|) zkWn(>8gzqwNIN_%{DYJ^@6RClO4#HMhr~dIgHQ0|>fAN+JnORGw+ZdOpIzrUZ%wYt z_$Y`#h0gCsji=a%))Lj|(JH1Hg8ki0xPr7R0)^qA3D_sVNZL1&?L*B&Aue1P2yu#V zCO~+Zn@c`2RbUHNn7Aa;7OBIq4(Qt-R#e}?*iS+4HQ1dA1T&TJR20}WGE`vYPoWig zLI`{kLc-vAHJ(pH$lZa>1{%kU_L(t=MbPhzoRM-mN6-ssB=JMUVxi7K!F@E%QQj>P zJJVDSLRW#c51~`&K~9on#4w+eBu#Vl>9(am31K<T$n$~S#fRPU0fDQ#d$*iBAa@0Q zy*l*G7=Wv8Ik6>@WK85v4*IU3o(I>Hr7(On0SV#BH4h+u0c?+Zc!L^t>OoOd3XIS) zAP1rX-mnzxw)rtMrLn20aWy%|oh526W+0asxz1d{0P}^hV#lZVKiyGbgn*e3kslhH zXkAld{~U4-^aQENIqTbkfn3<oJpu?)3BBTpQ!8R)SDYex=*0B4u<aKa2B)4uH<i6& zg5da2u!!Y}i+_|H1CPo006t1G*ZL#(RT-(`>dHtsGi<@mouquHd8hSPuFZOlIl+Bh zMi!IBWxxqIVZ8=VIk<whF>Au%e1%|;O*6>05lBS5LN-t2G4XPb$aR7jEEC#OfTfJq zc&#>zyu!(7t`>g9ued$#_EfejLd<SLB_FWn+mp`l)_bYVh%Yx{P<dOY7bvJ!gebhE z4&WWMM?(`3d_6?8p=8a}*+WL(6M*qJ{P1Ry?f$^P05C+LjX()LcGUa_a3TR`8QVZW zZwp_FJpnzYdVfv>$)`fpmuKm0B3}!kSmXY-X0)OOCAnB0TF4<0mgy+4eB44x9AF|Q zaJG0fk`T+-jl{{4wFv|@m6oDSq!JO%maa{t3ayPoCHTXT*dy`b!E5ySa1Erx*M1F5 zH=K6cF!*%vZWxRMAIo4c4!(?I4R8ILuNCG2yb$x?*PbVVzo{L_M-JYUzp<@AJ`V!; z3O*EM;LRN^S3!kfFhcQ3G5cVh2J6RX!+30kVV0e+cF!3sey}*lzdxx9mSMzr#6h`* zSdQp`?b=>n+jBuYUSnC{%OIaS_`@xg4PdMkYXeuo_EbJ6=C;SD^QGGIVA}Y>T0&Tf z`3A4ihrB_9`3HT>V=vnu;!2_1FigXE_-wqvc<rCn3Y>0pSA;QQTclHDb(B}suBeNu zNY#4P<>-Lun&>Mr6Jq7DE7T(OGWAY%Ph4T#-ng$dnVJon^IBHhs=dQbWsk>a#`o&h z=#~0%{l^KSgp4r-WA+#%h6KYJ!~L-<j1r^4xb(Tq=ME+MCe|iyNxb=d`t#hlhVcm# zWD}N7xS5n`PCA+_P0mR^kbEh*H$|EflTwh<oU%9N{KSZf3nw0%lt1apWaH$EQ{1Oi zr!uKIsaMieX*FpVr)Ez*IZZe%bz1GTYw7vZ`)05+Iy1vEmt}6vyqjgnTAy{v6ksYh zwU~~YdS=RI=FQwW^H#Pxdv12;EZ<pmvv$n7ol};xD_5RtmV;B9+nbl3w|91PzE}Q% z{D*V$=Nv7N6eJYX7xceyW^T~j!nv*Ug!9VgwH1aGE-AeEqVmNxFWxOmEUGNpU38~d zUA(ZkqxfV=V#%_Su2N-bLFu_NQCUt|ec7e?PV;s1m&`v?E-5c4-%@_NLRYb>Qe0V3 zd2PYy1)CT2EZksT<hn?=sH4iSs%5c!amy0flE#-5FKu4B>*cL4_bi*Rta(}2a>eo` z%XcjAT2ZqyY-Qfctt-#1?5hr{POaWreb$_@Dqz*o)#Fz0Ufs85>YBzi7hj$DYU`_4 zYLnNxukBu^ShsuK_18wf*6`Yu^}g$utUvjuh(FcUiR#wX^}fFM^;`9tdUHelnKzu? zXxk96p?1UBHwAA_esgyNxAAaeWm8piWs9&SvE@KZ_a^10wpONfVe93$Qs3%no7&d- zw&d;gZ|~jQ`%Y$iSo^kj!`^Lp_hv^?NB^ITwg|RlY}xmo`n{I-E^aM)zi69$+ktJj zwwt!!-m!GYo*lh^Dfmmv&WStgcJ_Us`=HjmOS7xx!-fxgb_eY~y!-B7+x7_dEc=`0 zZ|!^4dpq}K@7I0g^s(~e{)71kyAI_YI`c{1CtE+cb@=Gv?jw>T6OWwj%<0_KdHvI+ zpY|Lr{4Dje%g17l?LKzxxZ!x&@uMe{Cpu1cp9(nDdRlh6@$}8lFP&NO59L4F&I-?F zp55|=>le$;`JFTG`DeyI@0>TCKYStNLjHw%{_ohu$cx)9a$lBz*>frEQqHB;OFdud zzRI~=b@|LUwcniUl6U2IZR@)Et>WACZx3A=eWmEi*6$L&>-g?rcgt0$tA$rPzmNF7 z?g!r=+OB0^yZ&S2^^oh|-$=c2`eyCT&Y!$~x_m3+*14Z$KR5K0_Z&6<lK9K!+mhQk zw-5jO>pP9V2K~DEE_?UT-Jahfep~h1+1`-eRrkdA)c30HHQaZ<-+KS%1Nnox2m2oC z9v*)reAL_*(pTAc=CR;$^5d=jllxCwW!6M%^MCup(Slja_`r%AKRBY`2m(qTjCjp| zJOt-0t2UGEfmA?X6ge$$V%Edd3B#WY#7u)N{4Xj{Fm0BIcR*@LYsq<sd^AJ@`UheK zN`V^Klwd(_e^5KDhH@u>)L${grh?tLCH7P$q_eQ*kA<C-1lU<c(-3wP?1j#Qze>oB zxC$^uJArqrAkTEc0x<kp3@HmCPnm$(c$M*En6>*ay-PSSgY$3Z4)ZH>m-&t9W$rQe znFq{6<`L7!JZAbCE5k7ZQ~+jh8A_>;I#3ZDigAKzkQgkJxl%XkPC+;yCPh-}NxkS$ zDx=<1PJO6?4x_$wI9Oa8L6y{>2GBqnM1x^tA(V#Ea2i2J(nuOb%_<sAV`wZ@(>SW3 zTFTORs-t?EKu6KhbPNThgVac$qlxr+I*yK~6KE1mrYUqHokS<oDKwR)(W!JAO{ddo z289FCG>e+(Oqxw+(Hxpf^XP1vPv_7A`U0Ix=g~s?A}ylDw1k$@GCE&E%V`CzqzmXm zx`<ZM#rrS5M?fe;Sg4MT5zf}=Sq!N$q(et9oUM(41Z_+#I*_1^i9<(&jussjop^Kr zp^ecyESXonco76+5rSoDV-XUIkXVGoA|w_eu?UIP3(d2&YP^dYA!>xE5u!$j8X=$* z3mk-~5u(O=#UUaN5y0vO2N7|Ih(kmiB0#AgIEaWtga#2BL}(BJYP-Negjs_S4MH>s z(I7;F5G_Kq2+<-$ix4eBw206mLW>A3BD9EL5y2vYMFfio77;8WSVXXhU=hLUp}pA{ zY=0gSuf`BU;t>*$5HQ;U4npD)5|200;Z1aS6CK_}hX@@abcoO)LWc+)B6Nt*BSMb| zJtFjo&?7<*2(uPmJOmK}FTPM6KhJS-dGQ3bF2?Qykj2-};_GK)H1=q%-3K&_M8F~u zurY8$7U_va8eowISfl|KX@Er<V37t`qyZLbU<?aHFq=ghV37t`qyZLbfJGW$kp@_# V0TyY1MH*n$dI8Bys}C0l{tx-ZYaRdq delta 5319 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 diff --git a/view/theme/vier/font/fontawesome-webfont.woff b/view/theme/vier/font/fontawesome-webfont.woff old mode 100755 new mode 100644 index 628b6a52a87e62c6f22426e17c01f6a303aa194e..dc35ce3c2cf688c89b0bd0d4a82bc4be82b14c40 GIT binary patch literal 83588 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�>=olG4!T4BYNCc4f{&*Kgc`m= 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 L4D;{PWMBXQ&i-xA literal 65452 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	q%u^)w#YX9ZIvhtu?9Cy6YRi6f6G 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 diff --git a/view/theme/vier/font/fontawesome-webfont.woff2 b/view/theme/vier/font/fontawesome-webfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..500e5172534171f678e01f7569d66f9257036a09 GIT binary patch literal 66624 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 Ugj70-0RR91;1A}g`?1ym0Nn2un*aa+ literal 0 HcmV?d00001 diff --git a/view/theme/vier/templates/wall_thread.tpl b/view/theme/vier/templates/wall_thread.tpl index 267b35df77..c9ee770819 100644 --- a/view/theme/vier/templates/wall_thread.tpl +++ b/view/theme/vier/templates/wall_thread.tpl @@ -91,7 +91,7 @@ {{if $item.threaded}} {{/if}} {{if $item.comment}} - <a role="button" id="comment-{{$item.id}}" class="fakelink togglecomment" onclick="openClose('item-comments-{{$item.id}}'); commentExpand({{$item.id}});" title="{{$item.switchcomment}}"><i class="icon-reply"><span class="sr-only">{{$item.switchcomment}}</span></i></a> + <a role="button" id="comment-{{$item.id}}" class="fakelink togglecomment" onclick="openClose('item-comments-{{$item.id}}'); commentExpand({{$item.id}});" title="{{$item.switchcomment}}"><i class="icon-commenting"><span class="sr-only">{{$item.switchcomment}}</span></i></a> {{/if}} {{if $item.isevent}} From 71227b5d0d5a0110354ec9d5e928cdeb0a832026 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 00:53:30 +0100 Subject: [PATCH 157/273] Receiving should be complete, sending partially works --- include/diaspora2.php | 539 ++++++++++++++++++++++++++++++++++++++---- include/xml.php | 39 +++ 2 files changed, 538 insertions(+), 40 deletions(-) create mode 100644 include/xml.php diff --git a/include/diaspora2.php b/include/diaspora2.php index 939a816f40..197cb1da11 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -11,44 +11,8 @@ require_once("include/Contact.php"); require_once("include/Photo.php"); require_once("include/socgraph.php"); require_once("include/group.php"); -require_once("include/api.php"); - -/** - * @brief This class contain functions to work with XML data - * - */ -class xml { - function from_array($array, &$xml) { - - if (!is_object($xml)) { - foreach($array as $key => $value) { - $root = new SimpleXMLElement("<".$key."/>"); - array_to_xml($value, $root); - - $dom = dom_import_simplexml($root)->ownerDocument; - $dom->formatOutput = true; - return $dom->saveXML(); - } - } - - foreach($array as $key => $value) { - if (!is_array($value) AND !is_numeric($key)) - $xml->addChild($key, $value); - elseif (is_array($value)) - array_to_xml($value, $xml->addChild($key)); - } - } - - function copy(&$source, &$target, $elementname) { - if (count($source->children()) == 0) - $target->addChild($elementname, $source); - else { - $child = $target->addChild($elementname); - foreach ($source->children() AS $childfield => $childentry) - self::copy($childentry, $child, $childfield); - } - } -} +require_once("include/xml.php"); +require_once("include/datetime.php"); /** * @brief This class contain functions to create and send Diaspora XML files @@ -56,6 +20,50 @@ class xml { */ class diaspora { + public static function fetch_relay() { + + $serverdata = get_config("system", "relay_server"); + if ($serverdata == "") + return array(); + + $relay = array(); + + $servers = explode(",", $serverdata); + + foreach($servers AS $server) { + $server = trim($server); + $batch = $server."/receive/public"; + + $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); + + if (!$relais) { + $addr = "relay@".str_replace("http://", "", normalise_link($server)); + + $r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`) + VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')", + datetime_convert(), + dbesc($addr), + dbesc($addr), + dbesc($server), + dbesc(normalise_link($server)), + dbesc($batch), + dbesc(NETWORK_DIASPORA), + intval(CONTACT_IS_FOLLOWER), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()) + ); + + $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); + if ($relais) + $relay[] = $relais[0]; + } else + $relay[] = $relais[0]; + } + + return $relay; + } + /** * @brief Dispatches public messages and find the fitting receivers * @@ -1180,6 +1188,9 @@ EOT; } private function receive_request_make_friend($importer, $contact) { + + $a = get_app(); + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", intval(CONTACT_IS_FRIEND), @@ -1204,7 +1215,7 @@ EOT; if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) { $arr = array(); - $arr["uri"] = $arr["parent-uri"] = item_new_uri(App::get_hostname(), $importer["uid"]); + $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]); $arr["uid"] = $importer["uid"]; $arr["contact-id"] = $self[0]["id"]; $arr["wall"] = 1; @@ -1369,7 +1380,7 @@ EOT; // Maybe it is already a reshared item? // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - if (api_share_as_retweet($r[0])) + if (self::is_reshare($r[0]["body"])) $r = array(); else return $r[0]; @@ -1639,5 +1650,453 @@ EOT; return $message_id; } + + /******************************************************************************************* + * Here come all the functions that are needed to transmit data with the Diaspora protocol * + *******************************************************************************************/ + + private function get_my_handle($me) { + if ($contact["addr"] != "") + return $contact["addr"]; + + // Normally we should have a filled "addr" field - but in the past this wasn't the case + // So - just in case - we build the the address here. + return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); + } + + function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { + + logger("Message: ".$msg, LOGGER_DATA); + + $handle = self::get_my_handle($user); + + $b64url_data = base64url_encode($msg); + + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); + + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; + + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); + + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); + +$magic_env = <<< EOT +<?xml version='1.0' encoding='UTF-8'?> +<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" > + <header> + <author_id>$handle</author_id> + </header> + <me:env> + <me:encoding>base64url</me:encoding> + <me:alg>RSA-SHA256</me:alg> + <me:data type="application/xml">$data</me:data> + <me:sig>$sig</me:sig> + </me:env> +</diaspora> +EOT; + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { + + logger("Message: ".$msg, LOGGER_DATA); + + // without a public key nothing will work + + if (!$pubkey) { + logger("pubkey missing: contact id: ".$contact["id"]); + return false; + } + + $inner_aes_key = random_string(32); + $b_inner_aes_key = base64_encode($inner_aes_key); + $inner_iv = random_string(16); + $b_inner_iv = base64_encode($inner_iv); + + $outer_aes_key = random_string(32); + $b_outer_aes_key = base64_encode($outer_aes_key); + $outer_iv = random_string(16); + $b_outer_iv = base64_encode($outer_iv); + + $handle = self::get_my_handle($user); + + $padded_data = pkcs5_pad($msg,16); + $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); + + $b64_data = base64_encode($inner_encrypted); + + + $b64url_data = base64url_encode($b64_data); + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); + + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; + + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); + + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); + +$decrypted_header = <<< EOT +<decrypted_header> + <iv>$b_inner_iv</iv> + <aes_key>$b_inner_aes_key</aes_key> + <author_id>$handle</author_id> +</decrypted_header> +EOT; + + $decrypted_header = pkcs5_pad($decrypted_header,16); + + $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); + + $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key)); + + $encrypted_outer_key_bundle = ""; + openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey); + + $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); + + logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA); + + $encrypted_header_json_object = json_encode(array("aes_key" => base64_encode($encrypted_outer_key_bundle), + "ciphertext" => base64_encode($ciphertext))); + $cipher_json = base64_encode($encrypted_header_json_object); + + $encrypted_header = "<encrypted_header>".$cipher_json."</encrypted_header>"; + +$magic_env = <<< EOT +<?xml version='1.0' encoding='UTF-8'?> +<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" > + $encrypted_header + <me:env> + <me:encoding>base64url</me:encoding> + <me:alg>RSA-SHA256</me:alg> + <me:data type="application/xml">$data</me:data> + <me:sig>$sig</me:sig> + </me:env> +</diaspora> +EOT; + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { + + if ($public) + $magic_env = self::build_public_message($msg,$user,$contact,$prvkey,$pubkey); + else + $magic_env = self::build_private_message($msg,$user,$contact,$prvkey,$pubkey); + + // The data that will be transmitted is double encoded via "urlencode", strange ... + $slap = "xml=".urlencode(urlencode($magic_env)); + return $slap; + } + + private function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { + + $a = get_app(); + + $enabled = intval(get_config("system", "diaspora_enabled")); + if(!$enabled) + return 200; + + $logid = random_string(4); + $dest_url = (($public_batch) ? $contact["batch"] : $contact["notify"]); + if (!$dest_url) { + logger("no url for contact: ".$contact["id"]." batch mode =".$public_batch); + return 0; + } + + logger("transmit: ".$logid."-".$guid." ".$dest_url); + + if (!$queue_run && was_recently_delayed($contact["id"])) { + $return_code = 0; + } else { + if (!intval(get_config("system", "diaspora_test"))) { + post_url($dest_url."/", $slap); + $return_code = $a->get_curl_code(); + } else { + logger("test_mode"); + return 200; + } + } + + logger("transmit: ".$logid."-".$guid." returns: ".$return_code); + + if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) { + logger("queue message"); + + $r = q("SELECT `id` FROM `queue` WHERE `cid` = %d AND `network` = '%s' AND `content` = '%s' AND `batch` = %d LIMIT 1", + intval($contact["id"]), + dbesc(NETWORK_DIASPORA), + dbesc($slap), + intval($public_batch) + ); + if(count($r)) { + logger("add_to_queue ignored - identical item already in queue"); + } else { + // queue message for redelivery + add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch); + } + } + + + return(($return_code) ? $return_code : (-1)); + } + + public static function send_share($me,$contact) { + $myaddr = self::get_my_handle($me); + $theiraddr = $contact["addr"]; + + $data = array("XML" => array("post" => array("request" => array( + "sender_handle" => $myaddr, + "recipient_handle" => $theiraddr + )))); + + $msg = xml::from_array($data, $xml); + + $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + + return(self::transmit($owner,$contact,$slap, false)); + + } + + public static function send_unshare($me,$contact) { + $myaddr = self::get_my_handle($me); + + $data = array("XML" => array("post" => array("retraction" => array( + "post_guid" => $me["guid"], + "diaspora_handle" => $myaddr, + "type" => "Person" + )))); + + $msg = xml::from_array($data, $xml); + + $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + + return(self::transmit($owner,$contact,$slap, false)); + } + + function is_reshare($body) { + $body = trim($body); + + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(false); + + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(false); + + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + // Skip if there is no shared message in there + if ($body == $attributes) + return(false); + + $guid = ""; + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + preg_match('/guid="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + if ($guid != "") { + $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", + dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); + if ($r) { + $ret= array(); + $ret["root_handle"] = diaspora_handle_from_contact($r[0]["contact-id"]); + $ret["root_guid"] = $guid; + return($ret); + } + } + + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + $ret= array(); + + $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); + if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) + return(false); + + $link = ""; + preg_match("/link='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + preg_match('/link="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); + if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) + return(false); + return($ret); + } + + function send_status($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::get_my_handle($owner); + $theiraddr = $contact["addr"]; + + $title = $item["title"]; + $body = $item["body"]; + + // convert to markdown + $body = html_entity_decode(bb2diaspora($body)); + + // Adding the title + if(strlen($title)) + $body = "## ".html_entity_decode($title)."\n\n".$body; + + if ($item["attach"]) { + $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); + if(cnt) { + $body .= "\n".t("Attachments:")."\n"; + foreach($matches as $mtch) + $body .= "[".$mtch[3]."](".$mtch[1].")\n"; + } + } + + + $public = (($item["private"]) ? "false" : "true"); + + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + // Detect a share element and do a reshare + if (!$item['private'] AND ($ret = self::is_reshare($item["body"]))) { + $message = array("root_diaspora_id" => $ret["root_handle"], + "root_guid" => $ret["root_guid"], + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + $data = array("XML" => array("post" => array("reshare" => $message))); + } else { + $location = array(); + + if ($item["location"] != "") + $location["address"] = $item["location"]; + + if ($item["coord"] != "") { + $coord = explode(" ", $item["coord"]); + $location["lat"] = $coord[0]; + $location["lng"] = $coord[1]; + } + + $message = array("raw_message" => $body, + "location" => $location, + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + if (count($location) == 0) + unset($message["location"]); + + $data = array("XML" => array("post" => array("status_message" => $message))); + } + + $msg = xml::from_array($data, $xml); + + logger("status: ".$owner["username"]." -> ".$contact["name"]." base message: ".$msg, LOGGER_DATA); + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); + + $return_code = self::transmit($owner,$contact,$slap, false); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + + function send_mail($item,$owner,$contact) { + + $myaddr = self::get_my_handle($owner); + + $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($item["convid"]), + intval($item["uid"]) + ); + + if (!count($r)) { + logger("conversation not found."); + return; + } + $cnv = $r[0]; + + $conv = array( + "guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"] + ); + + $body = bb2diaspora($item["body"]); + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; + + $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + + $msg = array( + "guid" => $item["guid"], + "parent_guid" => $cnv["guid"], + "parent_author_signature" => $sig, + "author_signature" => $sig, + "text" => $body, + "created_at" => $created, + "diaspora_handle" => $myaddr, + "conversation_guid" => $cnv["guid"] + ); + + if ($item["reply"]) + $data = array("XML" => array("post" => array("message" => $msg))); + else { + $message = array("guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "message" => $msg, + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"]); + + $data = array("XML" => array("post" => array("conversation" => $message))); + } + + $xmsg = xml::from_array($data, $xml); + + logger("conversation: ".print_r($xmsg,true), LOGGER_DATA); + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($xmsg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], false); + + $return_code = self::transmit($owner, $contact, $slap, false, false, $item["guid"]); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } } ?> diff --git a/include/xml.php b/include/xml.php new file mode 100644 index 0000000000..e46f53acc0 --- /dev/null +++ b/include/xml.php @@ -0,0 +1,39 @@ +<?php +/** + * @brief This class contain functions to work with XML data + * + */ +class xml { + function from_array($array, &$xml) { + + if (!is_object($xml)) { + foreach($array as $key => $value) { + $root = new SimpleXMLElement("<".$key."/>"); + self::from_array($value, $root); + + $dom = dom_import_simplexml($root)->ownerDocument; + $dom->formatOutput = true; + $xml = $dom; + return $dom->saveXML(); + } + } + + foreach($array as $key => $value) { + if (!is_array($value) AND !is_numeric($key)) + $xml->addChild($key, $value); + elseif (is_array($value)) + self::from_array($value, $xml->addChild($key)); + } + } + + function copy(&$source, &$target, $elementname) { + if (count($source->children()) == 0) + $target->addChild($elementname, $source); + else { + $child = $target->addChild($elementname); + foreach ($source->children() AS $childfield => $childentry) + self::copy($childentry, $child, $childfield); + } + } +} +?> From 6468fbb9051ab6fe09bfcaaf0f46ce03491ace4f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 01:37:47 +0100 Subject: [PATCH 158/273] Retraction could work now as well. --- include/diaspora2.php | 34 ++++++++++++++++++++ include/xml.php | 2 +- view/templates/diaspora_relay_retraction.tpl | 5 ++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 197cb1da11..c745ab8366 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2031,6 +2031,40 @@ EOT; return $return_code; } + function send_retraction($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::get_my_handle($owner); + + // Check whether the retraction is for a top-level post or whether it's a relayable + if ($item["uri"] !== $item["parent-uri"]) { + $msg_type = "relayable_retraction"; + $target_type = (($item["verb"] === ACTIVITY_LIKE) ? "Like" : "Comment"); + } else { + $msg_type = "signed_retraction"; + $target_type = "StatusMessage"; + } + + $signed_text = $item["guid"].";".$target_type; + + $message = array("target_guid" => $item['guid'], + "target_type" => $target_type, + "sender_handle" => $myaddr, + "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); + + $data = array("XML" => array("post" => array($msg_type => $message))); + $msg = xml::from_array($data, $xml); + + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); + + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + function send_mail($item,$owner,$contact) { $myaddr = self::get_my_handle($owner); diff --git a/include/xml.php b/include/xml.php index e46f53acc0..9c458dab12 100644 --- a/include/xml.php +++ b/include/xml.php @@ -20,7 +20,7 @@ class xml { foreach($array as $key => $value) { if (!is_array($value) AND !is_numeric($key)) - $xml->addChild($key, $value); + $xml->addChild($key, xmlify($value)); elseif (is_array($value)) self::from_array($value, $xml->addChild($key)); } diff --git a/view/templates/diaspora_relay_retraction.tpl b/view/templates/diaspora_relay_retraction.tpl index b3f97a2e13..c4b44cd05f 100644 --- a/view/templates/diaspora_relay_retraction.tpl +++ b/view/templates/diaspora_relay_retraction.tpl @@ -1,11 +1,10 @@ - <XML> <post> <relayable_retraction> - <target_type>{{$type}}</target_type> <target_guid>{{$guid}}</target_guid> - <target_author_signature>{{$signature}}</target_author_signature> + <target_type>{{$type}}</target_type> <sender_handle>{{$handle}}</sender_handle> + <target_author_signature>{{$signature}}</target_author_signature> </relayable_retraction> </post> </XML> From af7b028b13b411bb241dacf488b1085dbaf26780 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 11:41:51 +0100 Subject: [PATCH 159/273] Preparation for followups --- include/diaspora2.php | 77 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index c745ab8366..ef1b1c3801 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1664,7 +1664,7 @@ EOT; return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); } - function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { + private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { logger("Message: ".$msg, LOGGER_DATA); @@ -1884,7 +1884,7 @@ EOT; return(self::transmit($owner,$contact,$slap, false)); } - function is_reshare($body) { + private function is_reshare($body) { $body = trim($body); // Skip if it isn't a pure repeated messages @@ -1951,7 +1951,7 @@ EOT; return($ret); } - function send_status($item, $owner, $contact, $public_batch = false) { + public static function send_status($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); $theiraddr = $contact["addr"]; @@ -2030,8 +2030,75 @@ EOT; return $return_code; } +/* + public static function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { - function send_retraction($item, $owner, $contact, $public_batch = false) { + $myaddr = self::get_my_handle($owner); + + // Diaspora doesn't support threaded comments, but some + // versions of Diaspora (i.e. Diaspora-pistos) support + // likes on comments + if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { + $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", + dbesc($item['thr-parent']) + ); + } else { + // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always + // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. + // The only item with `parent` and `id` as the parent id is the parent item. + $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", + intval($item['parent']), + intval($item['parent']) + ); + } + if(count($p)) + $parent = $p[0]; + else + return; + + if($item['verb'] === ACTIVITY_LIKE) { + $tpl = get_markup_template('diaspora_like.tpl'); + $like = true; + $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); + $positive = 'true'; + + if(($item['deleted'])) + logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction'); + } else { + $tpl = get_markup_template('diaspora_comment.tpl'); + $like = false; + } + + $text = html_entity_decode(bb2diaspora($item['body'])); + + // sign it + + if($like) + $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; + else + $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; + + $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + + $msg = replace_macros($tpl,array( + '$guid' => xmlify($item['guid']), + '$parent_guid' => xmlify($parent['guid']), + '$target_type' =>xmlify($target_type), + '$authorsig' => xmlify($authorsig), + '$body' => xmlify($text), + '$positive' => xmlify($positive), + '$handle' => xmlify($myaddr) + )); + + logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); + + $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); + + return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); + } +*/ + public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); @@ -2065,7 +2132,7 @@ EOT; return $return_code; } - function send_mail($item,$owner,$contact) { + public static function send_mail($item,$owner,$contact) { $myaddr = self::get_my_handle($owner); From 8752ec11b244e975bbf2c7589e859bfba38977d3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 13:15:27 +0100 Subject: [PATCH 160/273] Central function for default group, special setting for OStatus --- include/diaspora.php | 8 +++----- include/follow.php | 8 +++----- include/group.php | 32 ++++++++++++++++++++++++++++++-- mod/dfrn_confirm.php | 9 +++------ mod/dfrn_request.php | 20 ++++++++------------ mod/settings.php | 4 ++++ 6 files changed, 51 insertions(+), 30 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 93fe2a472f..635c1aabc3 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -700,12 +700,10 @@ function diaspora_request($importer,$xml) { return; } - $g = q("select def_gid from user where uid = %d limit 1", - intval($importer['uid']) - ); - if($g && intval($g[0]['def_gid'])) { + $def_gid = get_default_group($importer['uid'], $ret["network"]); + if (intval($def_gid)) { require_once('include/group.php'); - group_add_member($importer['uid'],'',$contact_record['id'],$g[0]['def_gid']); + group_add_member($importer['uid'], '', $contact_record['id'], $def_gid); } if($importer['page-flags'] == PAGE_NORMAL) { diff --git a/include/follow.php b/include/follow.php index 410e0e58aa..3af629536d 100644 --- a/include/follow.php +++ b/include/follow.php @@ -258,12 +258,10 @@ function new_contact($uid,$url,$interactive = false) { $contact_id = $r[0]['id']; $result['cid'] = $contact_id; - $g = q("select def_gid from user where uid = %d limit 1", - intval($uid) - ); - if($g && intval($g[0]['def_gid'])) { + $def_gid = get_default_group($uid, $contact["network"]); + if (intval($def_gid)) { require_once('include/group.php'); - group_add_member($uid,'',$contact_id,$g[0]['def_gid']); + group_add_member($uid, '', $contact_id, $def_gid); } require_once("include/Photo.php"); diff --git a/include/group.php b/include/group.php index 2b872f16a7..00b66ad586 100644 --- a/include/group.php +++ b/include/group.php @@ -188,7 +188,7 @@ function group_public_members($gid) { } -function mini_group_select($uid,$gid = 0) { +function mini_group_select($uid,$gid = 0, $label = "") { $grps = array(); $o = ''; @@ -205,8 +205,11 @@ function mini_group_select($uid,$gid = 0) { } logger('groups: ' . print_r($grps,true)); + if ($label == "") + $label = t('Default privacy group for new contacts'); + $o = replace_macros(get_markup_template('group_selection.tpl'), array( - '$label' => t('Default privacy group for new contacts'), + '$label' => $label, '$groups' => $grps )); return $o; @@ -375,3 +378,28 @@ function groups_count_unseen() { return $r; } + +/** + * @brief Returns the default group for a given user and network + * + * @param int $uid User id + * @param string $network network name + * + * @return int group id + */ +function get_default_group($uid, $network = "") { + + $default_group = 0; + + if ($network == NETWORK_OSTATUS) + $default_group = get_pconfig($uid, "ostatus", "default_group"); + + if ($default_group != 0) + return $default_group; + + $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid)); + if($g && intval($g[0]["def_gid"])) + $default_group = $g[0]["def_gid"]; + + return $default_group; +} diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php index 27c04a908d..68950ec285 100644 --- a/mod/dfrn_confirm.php +++ b/mod/dfrn_confirm.php @@ -489,13 +489,10 @@ function dfrn_confirm_post(&$a,$handsfree = null) { } } - - $g = q("select def_gid from user where uid = %d limit 1", - intval($uid) - ); - if($contact && $g && intval($g[0]['def_gid'])) { + $def_gid = get_default_group($uid, $contact["network"]); + if($contact && intval($def_gid)) { require_once('include/group.php'); - group_add_member($uid,'',$contact['id'],$g[0]['def_gid']); + group_add_member($uid, '', $contact['id'], $def_gid); } // Let's send our user to the contact editor in case they want to diff --git a/mod/dfrn_request.php b/mod/dfrn_request.php index 5455996069..837eec2dd2 100644 --- a/mod/dfrn_request.php +++ b/mod/dfrn_request.php @@ -174,18 +174,16 @@ function dfrn_request_post(&$a) { info( t("Introduction complete.") . EOL); } - $r = q("select id from contact where uid = %d and url = '%s' and `site-pubkey` = '%s' limit 1", + $r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `site-pubkey` = '%s' LIMIT 1", intval(local_user()), dbesc($dfrn_url), $parms['key'] // this was already escaped ); if(count($r)) { - $g = q("select def_gid from user where uid = %d limit 1", - intval(local_user()) - ); - if($g && intval($g[0]['def_gid'])) { + $def_gid = get_default_group(local_user(), $r[0]["network"]); + if(intval($def_gid)) { require_once('include/group.php'); - group_add_member(local_user(),'',$r[0]['id'],$g[0]['def_gid']); + group_add_member(local_user(), '', $r[0]['id'], $def_gid); } $forwardurl = $a->get_baseurl()."/contacts/".$r[0]['id']; } else @@ -388,19 +386,17 @@ function dfrn_request_post(&$a) { intval($rel) ); - $r = q("select id from contact where poll = '%s' and uid = %d limit 1", + $r = q("SELECT `id`, `network` FROM `contact` WHERE `poll` = '%s' AND `uid` = %d LIMIT 1", dbesc($poll), intval($uid) ); if(count($r)) { $contact_id = $r[0]['id']; - $g = q("select def_gid from user where uid = %d limit 1", - intval($uid) - ); - if($g && intval($g[0]['def_gid'])) { + $def_gid = get_default_group($uid, $r[0]["network"]); + if (intval($def_gid)) { require_once('include/group.php'); - group_add_member($uid,'',$contact_id,$g[0]['def_gid']); + group_add_member($uid, '', $contact_id, $def_gid); } $photo = avatar_img($addr); diff --git a/mod/settings.php b/mod/settings.php index 905a5ed08d..67513e533e 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -199,6 +199,7 @@ function settings_post(&$a) { if(x($_POST, 'general-submit')) { set_pconfig(local_user(), 'system', 'no_intelligent_shortening', intval($_POST['no_intelligent_shortening'])); set_pconfig(local_user(), 'system', 'ostatus_autofriend', intval($_POST['snautofollow'])); + set_pconfig(local_user(), 'ostatus', 'default_group', $_POST['group-selection']); set_pconfig(local_user(), 'ostatus', 'legacy_contact', $_POST['legacy_contact']); } elseif(x($_POST, 'imap-submit')) { @@ -797,8 +798,11 @@ function settings_content(&$a) { $settings_connectors .= '<span class="field_help">'.t('If you receive a message from an unknown OStatus user, this option decides what to do. If it is checked, a new contact will be created for every unknown user.').'</span>'; $settings_connectors .= '</div>'; + $default_group = get_pconfig(local_user(), 'ostatus', 'default_group'); $legacy_contact = get_pconfig(local_user(), 'ostatus', 'legacy_contact'); + $settings_connectors .= mini_group_select(local_user(), $default_group, t("Default group for OStatus followers")); + if ($legacy_contact != "") $a->page['htmlhead'] = '<meta http-equiv="refresh" content="0; URL='.$a->get_baseurl().'/ostatus_subscribe?url='.urlencode($legacy_contact).'">'; From 4ef33b4d5b874931a5b40600ecbf189b9046693d Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 13:26:30 +0100 Subject: [PATCH 161/273] Clarification of description --- mod/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/settings.php b/mod/settings.php index 67513e533e..0c3b23a44b 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -801,7 +801,7 @@ function settings_content(&$a) { $default_group = get_pconfig(local_user(), 'ostatus', 'default_group'); $legacy_contact = get_pconfig(local_user(), 'ostatus', 'legacy_contact'); - $settings_connectors .= mini_group_select(local_user(), $default_group, t("Default group for OStatus followers")); + $settings_connectors .= mini_group_select(local_user(), $default_group, t("Default group for OStatus contacts")); if ($legacy_contact != "") $a->page['htmlhead'] = '<meta http-equiv="refresh" content="0; URL='.$a->get_baseurl().'/ostatus_subscribe?url='.urlencode($legacy_contact).'">'; From bb06026147394db074312a8b77981f1df8b85294 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 14:09:04 +0100 Subject: [PATCH 162/273] Settings for only importing threads from our ostatus contacts --- mod/admin.php | 3 +++ view/templates/admin_site.tpl | 1 + 2 files changed, 4 insertions(+) diff --git a/mod/admin.php b/mod/admin.php index a98f464f81..f6958098b8 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -598,6 +598,7 @@ function admin_page_site_post(&$a) { $dfrn_only = ((x($_POST,'dfrn_only')) ? True : False); $ostatus_disabled = !((x($_POST,'ostatus_disabled')) ? True : False); $ostatus_poll_interval = ((x($_POST,'ostatus_poll_interval')) ? intval(trim($_POST['ostatus_poll_interval'])) : 0); + $ostatus_full_threads = ((x($_POST,'ostatus_full_threads')) ? True : False); $diaspora_enabled = ((x($_POST,'diaspora_enabled')) ? True : False); $ssl_policy = ((x($_POST,'ssl_policy')) ? intval($_POST['ssl_policy']) : 0); $force_ssl = ((x($_POST,'force_ssl')) ? True : False); @@ -746,6 +747,7 @@ function admin_page_site_post(&$a) { set_config('system','dfrn_only', $dfrn_only); set_config('system','ostatus_disabled', $ostatus_disabled); set_config('system','ostatus_poll_interval', $ostatus_poll_interval); + set_config('system','ostatus_full_threads', $ostatus_full_threads); set_config('system','diaspora_enabled', $diaspora_enabled); set_config('config','private_addons', $private_addons); @@ -947,6 +949,7 @@ function admin_page_site(&$a) { '$max_author_posts_community_page' => array('max_author_posts_community_page', t("Posts per user on community page"), get_config('system','max_author_posts_community_page'), t("The maximum number of posts per user on the community page. (Not valid for 'Global Community')")), '$ostatus_disabled' => array('ostatus_disabled', t("Enable OStatus support"), !get_config('system','ostatus_disabled'), t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")), '$ostatus_poll_interval' => array('ostatus_poll_interval', t("OStatus conversation completion interval"), (string) intval(get_config('system','ostatus_poll_interval')), t("How often shall the poller check for new entries in OStatus conversations? This can be a very ressource task."), $ostatus_poll_choices), + '$ostatus_full_threads' => array('ostatus_disabled', t("Only import OStatus threads from our contacts"), get_config('system','ostatus_full_threads'), t("Normally we import every content from our OStatus contacts. With this option we only store threads that are started by a contact that is known on our system.")), '$ostatus_not_able' => t("OStatus support can only be enabled if threading is enabled."), '$diaspora_able' => $diaspora_able, '$diaspora_not_able' => t("Diaspora support can't be enabled because Friendica was installed into a sub directory."), diff --git a/view/templates/admin_site.tpl b/view/templates/admin_site.tpl index b08e5f935f..91957d016a 100644 --- a/view/templates/admin_site.tpl +++ b/view/templates/admin_site.tpl @@ -87,6 +87,7 @@ {{if $thread_allow.2}} {{include file="field_checkbox.tpl" field=$ostatus_disabled}} {{include file="field_select.tpl" field=$ostatus_poll_interval}} + {{include file="field_checkbox.tpl" field=$ostatus_full_threads}} {{else}} <div class='field checkbox' id='div_id_{{$ostatus_disabled.0}}'> <label for='id_{{$ostatus_disabled.0}}'>{{$ostatus_disabled.1}}</label> From 4bd1061a095a0fd80c69e2151b3fcf0a232a0169 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 14:53:30 +0100 Subject: [PATCH 163/273] Only import OStatus threads if we follow the thread starter --- boot.php | 3 +++ include/ostatus.php | 7 +++++++ mod/admin.php | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/boot.php b/boot.php index 0a2f5c50cd..5cc3499311 100644 --- a/boot.php +++ b/boot.php @@ -1509,6 +1509,9 @@ function killme() { * @brief Redirect to another URL and terminate this process. */ function goaway($s) { + if (!strstr(normalise_link($s), normalise_link(App::get_baseurl()))) + $s = App::get_baseurl()."/".$s; + header("Location: $s"); killme(); } diff --git a/include/ostatus.php b/include/ostatus.php index 5c5016d0fc..40df688ea7 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -809,6 +809,13 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { // 2. This first post is a post inside our thread // 3. This first post is a post inside another thread if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { + + // Do we only want to import threads that were started by our contacts? + if (get_config('system','ostatus_full_threads')) { + logger("Don't import uri ".$first_id." because we don't follow this person.", LOGGER_DEBUG); + continue; + } + $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN (SELECT `parent` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1", diff --git a/mod/admin.php b/mod/admin.php index f6958098b8..e7c4f51b66 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -949,7 +949,7 @@ function admin_page_site(&$a) { '$max_author_posts_community_page' => array('max_author_posts_community_page', t("Posts per user on community page"), get_config('system','max_author_posts_community_page'), t("The maximum number of posts per user on the community page. (Not valid for 'Global Community')")), '$ostatus_disabled' => array('ostatus_disabled', t("Enable OStatus support"), !get_config('system','ostatus_disabled'), t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")), '$ostatus_poll_interval' => array('ostatus_poll_interval', t("OStatus conversation completion interval"), (string) intval(get_config('system','ostatus_poll_interval')), t("How often shall the poller check for new entries in OStatus conversations? This can be a very ressource task."), $ostatus_poll_choices), - '$ostatus_full_threads' => array('ostatus_disabled', t("Only import OStatus threads from our contacts"), get_config('system','ostatus_full_threads'), t("Normally we import every content from our OStatus contacts. With this option we only store threads that are started by a contact that is known on our system.")), + '$ostatus_full_threads' => array('ostatus_full_threads', t("Only import OStatus threads from our contacts"), get_config('system','ostatus_full_threads'), t("Normally we import every content from our OStatus contacts. With this option we only store threads that are started by a contact that is known on our system.")), '$ostatus_not_able' => t("OStatus support can only be enabled if threading is enabled."), '$diaspora_able' => $diaspora_able, '$diaspora_not_able' => t("Diaspora support can't be enabled because Friendica was installed into a sub directory."), From 7f48df63a3b278e1ff44d71b8cfdd8966fce2341 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 15:11:04 +0100 Subject: [PATCH 164/273] Relocated the check. --- include/ostatus.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 40df688ea7..8749d3ab21 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -810,12 +810,6 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { // 3. This first post is a post inside another thread if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { - // Do we only want to import threads that were started by our contacts? - if (get_config('system','ostatus_full_threads')) { - logger("Don't import uri ".$first_id." because we don't follow this person.", LOGGER_DEBUG); - continue; - } - $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN (SELECT `parent` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1", @@ -925,6 +919,12 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { } else { logger("No contact found for url ".$actor, LOGGER_DEBUG); + // Do we only want to import threads that were started by our contacts? + if (get_config('system','ostatus_full_threads')) { + logger("Don't import uri ".$first_id." because we don't follow this person.", LOGGER_DEBUG); + continue; + } + // Adding a global contact /// @TODO Use this data for the post $global_contact_id = get_contact($actor, 0); From 279a0453f9d338bae467db4c0aee938798e7c8f3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 15:13:19 +0100 Subject: [PATCH 165/273] Or back ... --- include/ostatus.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 8749d3ab21..40df688ea7 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -810,6 +810,12 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { // 3. This first post is a post inside another thread if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { + // Do we only want to import threads that were started by our contacts? + if (get_config('system','ostatus_full_threads')) { + logger("Don't import uri ".$first_id." because we don't follow this person.", LOGGER_DEBUG); + continue; + } + $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN (SELECT `parent` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1", @@ -919,12 +925,6 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { } else { logger("No contact found for url ".$actor, LOGGER_DEBUG); - // Do we only want to import threads that were started by our contacts? - if (get_config('system','ostatus_full_threads')) { - logger("Don't import uri ".$first_id." because we don't follow this person.", LOGGER_DEBUG); - continue; - } - // Adding a global contact /// @TODO Use this data for the post $global_contact_id = get_contact($actor, 0); From 9579725d4deafdbf123fd2c882be0cff93af4b5b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 19:27:34 +0100 Subject: [PATCH 166/273] This should work better ... --- include/ostatus.php | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 40df688ea7..44431967ec 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -686,7 +686,8 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { $conversation_url = ostatus_convert_href($conversation_url); // If the thread shouldn't be completed then store the item and go away - if ((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) { + // Don't do a completion on liked content + if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR ($item["verb"] == ACTIVITY_LIKE)) { //$arr["app"] .= " (OStatus-NoCompletion)"; $item_stored = item_store($item, true); return($item_stored); @@ -725,7 +726,7 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { $pageno = 1; $items = array(); - logger('fetching conversation url '.$conv.' for user '.$uid); + logger('fetching conversation url '.$conv.' ('.$conversation_url.') for user '.$uid); do { $conv_arr = z_fetch_url($conv."?page=".$pageno); @@ -778,6 +779,8 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid)); $importer = $r[0]; + $new_parent = true; + foreach ($items as $single_conv) { // Update the gcontact table @@ -810,11 +813,7 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { // 3. This first post is a post inside another thread if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { - // Do we only want to import threads that were started by our contacts? - if (get_config('system','ostatus_full_threads')) { - logger("Don't import uri ".$first_id." because we don't follow this person.", LOGGER_DEBUG); - continue; - } + $new_parent = true; $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN (SELECT `parent` FROM `item` @@ -916,12 +915,14 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { if (isset($single_conv->actor->url)) $actor = $single_conv->actor->url; - $contact = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + $contact = q("SELECT `id`, `rel` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", $uid, normalise_link($actor), NETWORK_STATUSNET); if (count($contact)) { logger("Found contact for url ".$actor, LOGGER_DEBUG); $contact_id = $contact[0]["id"]; + + $not_following = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); } else { logger("No contact found for url ".$actor, LOGGER_DEBUG); @@ -932,6 +933,14 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); $contact_id = $parent["contact-id"]; + + $not_following = true; + } + + // Do we only want to import threads that were started by our contacts? + if ($not_following AND $new_parent AND get_config('system','ostatus_full_threads')) { + logger("Don't import uri ".$first_id." because we don't follow the person ".$actor, LOGGER_DEBUG); + continue; } $arr = array(); From ef2bc47cc6d8f6be3fda65aec366ad237396a52c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 20:36:28 +0100 Subject: [PATCH 167/273] New way of fetching the conversation id for thread completion --- include/ostatus.php | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 44431967ec..138f510906 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -537,7 +537,7 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { } else $item["parent-uri"] = $item["uri"]; - $item_id = ostatus_completion($conversation, $importer["uid"], $item); + $item_id = ostatus_completion($conversation, $importer["uid"], $item, $self); if (!$item_id) { logger("Error storing item", LOGGER_DEBUG); @@ -676,18 +676,49 @@ function ostatus_conv_fetch_actor($actor) { update_gcontact($contact); } +function ostatus_fetch_conversation($self, $conversation_url = "") { -function ostatus_completion($conversation_url, $uid, $item = array()) { + if ($conversation_url != "") { + $elements = explode(":", $conversation_url); + + if ((count($elements) <= 2) OR ($elements[0] != "tag")) + return $conversation_url; + } + + if ($self == "") + return ""; + + $json = str_replace(".atom", ".json", $self); + + $raw = fetch_url($json); + if ($raw == "") + return ""; + + $data = json_decode($raw); + if (!is_object($data)) + return ""; + + $conversation_id = $data->statusnet_conversation_id; + + $pos = strpos($self, "/api/statuses/show/"); + $base_url = substr($self, 0, $pos); + + return $base_url."/conversation/".$conversation_id; +} + +function ostatus_completion($conversation_url, $uid, $item = array(), $self = "") { $a = get_app(); $item_stored = -1; - $conversation_url = ostatus_convert_href($conversation_url); + //$conversation_url = ostatus_convert_href($conversation_url); + $conversation_url = ostatus_fetch_conversation($self, $conversation_url); // If the thread shouldn't be completed then store the item and go away // Don't do a completion on liked content - if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR ($item["verb"] == ACTIVITY_LIKE)) { + if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR + ($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) { //$arr["app"] .= " (OStatus-NoCompletion)"; $item_stored = item_store($item, true); return($item_stored); @@ -726,7 +757,7 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { $pageno = 1; $items = array(); - logger('fetching conversation url '.$conv.' ('.$conversation_url.') for user '.$uid); + logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid); do { $conv_arr = z_fetch_url($conv."?page=".$pageno); From 4ef44c67b84a9f8d66b91a61123b10fe2be75784 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 21:06:52 +0100 Subject: [PATCH 168/273] Added documentation --- include/ostatus.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 138f510906..54b70e6d6c 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -676,13 +676,21 @@ function ostatus_conv_fetch_actor($actor) { update_gcontact($contact); } -function ostatus_fetch_conversation($self, $conversation_url = "") { +/** + * @brief Fetches the conversation url for a given item link or conversation id + * + * @param string $self The link to the posting + * @param string $conversation_id The conversation id + * + * @return string The conversation url + */ +function ostatus_fetch_conversation($self, $conversation_id = "") { - if ($conversation_url != "") { - $elements = explode(":", $conversation_url); + if ($conversation_id != "") { + $elements = explode(":", $conversation_id); if ((count($elements) <= 2) OR ($elements[0] != "tag")) - return $conversation_url; + return $conversation_id; } if ($self == "") From 182bee265e3aa68bb9c85f732e8306ef28ab6b1f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 6 Mar 2016 22:49:50 +0100 Subject: [PATCH 169/273] Sending like and comment should work now. --- include/diaspora2.php | 131 ++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index ef1b1c3801..0ab28d7b2c 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2030,74 +2030,93 @@ EOT; return $return_code; } -/* - public static function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { + + private function construct_like($item,$owner,$contact,$public_batch = false) { $myaddr = self::get_my_handle($owner); - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"]) + ); + if(!$p) + return false; - if($item['verb'] === ACTIVITY_LIKE) { - $tpl = get_markup_template('diaspora_like.tpl'); - $like = true; - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); - $positive = 'true'; + $parent = $p[0]; - if(($item['deleted'])) - logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction'); - } else { - $tpl = get_markup_template('diaspora_comment.tpl'); - $like = false; - } - - $text = html_entity_decode(bb2diaspora($item['body'])); + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; // sign it + $signed_text = $positive.";".$item["guid"].";".$target_type.";".$parent["guid"].";".$myaddr; - if($like) - $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; - else - $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; + $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + $message = array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr); - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($myaddr) - )); + $data = array("XML" => array("post" => array("like" => $message))); - logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); - - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); + return xml::from_array($data, $xml); } -*/ + + private function construct_comment($item,$owner,$contact,$public_batch = false) { + + $myaddr = self::get_my_handle($owner); + + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) + ); + + if (!$p) + return false; + + $parent = $p[0]; + + $text = html_entity_decode(bb2diaspora($item["body"])); + + // sign it + $signed_text = $item["guid"].";".$parent["guid"].";".$text.";".$myaddr; + + $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + + $message = array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "text" => $text, + "diaspora_handle" => $myaddr); + + $data = array("XML" => array("post" => array("comment" => $message))); + + return xml::from_array($data, $xml); + } + + public static function send_followup($item,$owner,$contact,$public_batch = false) { + + if($item['verb'] === ACTIVITY_LIKE) + $msg = self::construct_like($item, $owner, $contact, $public_batch); + else + $msg = self::construct_comment($item, $owner, $contact, $public_batch); + + if (!$msg) + return $msg; + + logger("base message: ".$msg, LOGGER_DATA); + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); + + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); From db15f76177fedd9620ed62518d4c11c6ac493107 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 7 Mar 2016 01:34:06 +0100 Subject: [PATCH 170/273] Relaying is nearly done --- include/diaspora2.php | 219 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 203 insertions(+), 16 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 0ab28d7b2c..1cc6e55e3b 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2063,32 +2063,45 @@ EOT; return xml::from_array($data, $xml); } - private function construct_comment($item,$owner,$contact,$public_batch = false) { + private function construct_comment($item,$owner,$contact,$public_batch = false, $data = null) { - $myaddr = self::get_my_handle($owner); + if (is_array($data)) + $message = $data; + else { + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($item["parent"]), - intval($item["parent"]) - ); + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) + ); - if (!$p) - return false; + if (!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $text = html_entity_decode(bb2diaspora($item["body"])); + $text = html_entity_decode(bb2diaspora($item["body"])); + + $message = array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => "", + "text" => $text, + "diaspora_handle" => $myaddr); + } // sign it - $signed_text = $item["guid"].";".$parent["guid"].";".$text.";".$myaddr; + $sigmsg = $message; + unset($sigmsg["author_signature"]); + unset($sigmsg["parent_author_signature"]); + + $signed_text = implode(";", $sigmsg); $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - $message = array("guid" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "text" => $text, - "diaspora_handle" => $myaddr); + if ($message["author_signature"] == "") + $message["author_signature"] = $authorsig; + else + $message["parent_author_signature"] = $authorsig; $data = array("XML" => array("post" => array("comment" => $message))); @@ -2117,6 +2130,180 @@ EOT; return $return_code; } + function send_relay($item, $owner, $contact, $public_batch = false) { + + if ($item["deleted"]) + $sql_sign_id = "retract_iid"; + else + $sql_sign_id = "iid"; + + // fetch the original signature if the relayable was created by a Diaspora + // or DFRN user. + + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", + intval($item["id"]) + ); + + if(count($r)) { + $orig_sign = $r[0]; + $signed_text = $orig_sign['signed_text']; + $authorsig = $orig_sign['signature']; + $handle = $orig_sign['signer']; + + // Split the signed text + $signed_parts = explode(";", $signed_text); + + // Remove the comment guid + $guid = array_shift($signed_parts); + + // Remove the parent guid + $parent_guid = array_shift($signed_parts); + + // Remove the handle + $handle = array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); + + $data = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $orig_sign['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } + + + $myaddr = self::get_my_handle($owner); + + if ($item['deleted']) + ; // Retraction + elseif($item['verb'] === ACTIVITY_LIKE) + $msg = self::construct_like($item, $owner, $contact, $public_batch); + else + $msg = self::construct_comment($item, $owner, $contact, $public_batch, $data); +die($msg); +/* + // Diaspora doesn't support threaded comments, but some + // versions of Diaspora (i.e. Diaspora-pistos) support + // likes on comments + if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { + $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", + dbesc($item['thr-parent']) + ); + } + else { + // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always + // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. + // The only item with `parent` and `id` as the parent id is the parent item. + $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", + intval($item['parent']), + intval($item['parent']) + ); + } + if(count($p)) + $parent = $p[0]; + else + return; + + $like = false; + $relay_retract = false; + $sql_sign_id = 'iid'; + if( $item['deleted']) { + $relay_retract = true; + + $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); + + $sql_sign_id = 'retract_iid'; + $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); + } + elseif($item['verb'] === ACTIVITY_LIKE) { + $like = true; + + $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); +// $positive = (($item['deleted']) ? 'false' : 'true'); + $positive = 'true'; + + $tpl = get_markup_template('diaspora_like_relay.tpl'); + } + else { // item is a comment + $tpl = get_markup_template('diaspora_comment_relay.tpl'); + } + + + // fetch the original signature if the relayable was created by a Diaspora + // or DFRN user. Relayables for other networks are not supported. + + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1", + intval($item['id']) + ); + if(count($r)) { + $orig_sign = $r[0]; + $signed_text = $orig_sign['signed_text']; + $authorsig = $orig_sign['signature']; + $handle = $orig_sign['signer']; + + // Split the signed text + $signed_parts = explode(";", $signed_text); + + // Remove the parent guid + array_shift($signed_parts); + + // Remove the comment guid + array_shift($signed_parts); + + // Remove the handle + array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); + } + else { + // This part is meant for cases where we don't have the signatur. (Which shouldn't happen with posts from Diaspora and Friendica) + // This means that the comment won't be accepted by newer Diaspora servers + + $body = $item['body']; + $text = html_entity_decode(bb2diaspora($body)); + + $handle = diaspora_handle_from_contact($item['contact-id']); + if(! $handle) + return; + + if($relay_retract) + $signed_text = $item['guid'] . ';' . $target_type; + elseif($like) + $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle; + else + $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle; + + $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + } + + // Sign the relayable with the top-level owner's signature + $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + + $msg = replace_macros($tpl,array( + '$guid' => xmlify($item['guid']), + '$parent_guid' => xmlify($parent['guid']), + '$target_type' =>xmlify($target_type), + '$authorsig' => xmlify($authorsig), + '$parentsig' => xmlify($parentauthorsig), + '$body' => xmlify($text), + '$positive' => xmlify($positive), + '$handle' => xmlify($handle) + )); + + logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); + + $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); + //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)); + + return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); +*/ + } + + public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); From 373beb0343738fb357b5fbddd9ec76783b003ad5 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 7 Mar 2016 08:17:21 +0100 Subject: [PATCH 171/273] Relayed Likes and relayed comments should work, code needs beautification --- include/diaspora2.php | 122 +++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 48 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 1cc6e55e3b..48093cc5b9 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1799,6 +1799,16 @@ EOT; return $slap; } + private function get_signature($owner, $message) { + $sigmsg = $message; + unset($sigmsg["author_signature"]); + unset($sigmsg["parent_author_signature"]); + + $signed_text = implode(";", $sigmsg); + + return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } + private function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { $a = get_app(); @@ -2031,32 +2041,37 @@ EOT; return $return_code; } - private function construct_like($item,$owner,$contact,$public_batch = false) { + private function construct_like($item,$owner,$contact,$public_batch = false, $data = null) { - $myaddr = self::get_my_handle($owner); + if (is_array($data)) + $message = $data; + else { + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", - dbesc($item["thr-parent"]) - ); - if(!$p) - return false; + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"])); + if(!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); - $positive = "true"; + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; - // sign it - $signed_text = $positive.";".$item["guid"].";".$target_type.";".$parent["guid"].";".$myaddr; + $message = array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $target_type, + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr); + } - $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + $authorsig = self::get_signature($owner, $message); - $message = array("positive" => $positive, - "guid" => $item["guid"], - "target_type" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "diaspora_handle" => $myaddr); + if ($message["author_signature"] == "") + $message["author_signature"] = $authorsig; + else + $message["parent_author_signature"] = $authorsig; $data = array("XML" => array("post" => array("like" => $message))); @@ -2089,14 +2104,7 @@ EOT; "diaspora_handle" => $myaddr); } - // sign it - $sigmsg = $message; - unset($sigmsg["author_signature"]); - unset($sigmsg["parent_author_signature"]); - - $signed_text = implode(";", $sigmsg); - - $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + $authorsig = self::get_signature($owner, $message); if ($message["author_signature"] == "") $message["author_signature"] = $authorsig; @@ -2153,36 +2161,56 @@ EOT; // Split the signed text $signed_parts = explode(";", $signed_text); - // Remove the comment guid - $guid = array_shift($signed_parts); + if ($item['verb'] === ACTIVITY_LIKE) { + $data = array("positive" => $signed_parts[0], + "guid" => $signed_parts[1], + "target_type" => $signed_parts[2], + "parent_guid" => $signed_parts[3], + "parent_author_signature" => "", + "author_signature" => $orig_sign['signature'], + "diaspora_handle" => $signed_parts[4]); + } else { + // Remove the comment guid + $guid = array_shift($signed_parts); - // Remove the parent guid - $parent_guid = array_shift($signed_parts); + // Remove the parent guid + $parent_guid = array_shift($signed_parts); - // Remove the handle - $handle = array_pop($signed_parts); + // Remove the handle + $handle = array_pop($signed_parts); - // Glue the parts together - $text = implode(";", $signed_parts); + // Glue the parts together + $text = implode(";", $signed_parts); - $data = array("guid" => $guid, - "parent_guid" => $parent_guid, - "parent_author_signature" => "", - "author_signature" => $orig_sign['signature'], - "text" => implode(";", $signed_parts), - "diaspora_handle" => $handle); + $data = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $orig_sign['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } } - - $myaddr = self::get_my_handle($owner); - if ($item['deleted']) - ; // Retraction + ; // Relayed Retraction elseif($item['verb'] === ACTIVITY_LIKE) - $msg = self::construct_like($item, $owner, $contact, $public_batch); + $msg = self::construct_like($item, $owner, $contact, $public_batch, $data); else $msg = self::construct_comment($item, $owner, $contact, $public_batch, $data); die($msg); + + logger('base message: '.$msg, LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); + + $slap = self::build_message($msg,$owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); + + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item['guid']); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + /* // Diaspora doesn't support threaded comments, but some // versions of Diaspora (i.e. Diaspora-pistos) support @@ -2301,8 +2329,6 @@ die($msg); return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); */ - } - public static function send_retraction($item, $owner, $contact, $public_batch = false) { From 16eb8fd9bf705ad5d4d9f739b9264d7c8d15dace Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 7 Mar 2016 15:20:48 +0100 Subject: [PATCH 172/273] Small things ... --- include/diaspora2.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 48093cc5b9..4c9d79912e 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1525,7 +1525,7 @@ EOT; // Formerly we stored the signed text, the signature and the author in different fields. // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", intval($r[0]["id"]), dbesc(json_encode($data)) ); @@ -1552,7 +1552,7 @@ EOT; case "StatusMessage": return self::item_retraction($importer, $contact, $data);; - case "Person": + case "Person": /// @todo an "unshare" shouldn't remove the contact contact_remove($contact["id"]); return true; From ec9c9f0be78f9db691ee1c7174f10a7850033717 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 8 Mar 2016 00:20:06 +0100 Subject: [PATCH 173/273] Don't create lock files if the process is called from the poller via the worker --- include/cron.php | 51 +++++++++++++++++++------------------ include/cronhooks.php | 49 ++++++++++++++++++----------------- include/discover_poco.php | 46 ++++++++++++++++++--------------- include/onepoll.php | 21 ++++++++------- include/ostatus.php | 16 ++++++++---- include/pubsubpublish.php | 25 ++++++++++-------- include/queue.php | 25 ++++++++++-------- include/update_gcontact.php | 21 ++++++++------- 8 files changed, 141 insertions(+), 113 deletions(-) diff --git a/include/cron.php b/include/cron.php index 3acf711dd1..6143281710 100644 --- a/include/cron.php +++ b/include/cron.php @@ -40,15 +40,33 @@ function cron_run(&$argv, &$argc){ load_config('config'); load_config('system'); - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $maxsysload = intval(get_config('system','maxloadavg')); + if($maxsysload < 1) + $maxsysload = 50; - $load = current_load(); - if($load) { - if(intval($load) > $maxsysload) { - logger('system: load ' . $load . ' too high. cron deferred to next scheduled run.'); - return; + $load = current_load(); + if($load) { + if(intval($load) > $maxsysload) { + logger('system: load '.$load.' too high. cron deferred to next scheduled run.'); + return; + } + } + + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, 'cron'); + if($pidfile->is_already_running()) { + logger("cron: Already running"); + if ($pidfile->running_time() > 9*60) { + $pidfile->kill(); + logger("cron: killed stale process"); + // Calling a new instance + proc_run('php','include/cron.php'); + } + exit; + } } } @@ -66,23 +84,6 @@ function cron_run(&$argv, &$argc){ } } - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'cron'); - if($pidfile->is_already_running()) { - logger("cron: Already running"); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("cron: killed stale process"); - // Calling a new instance - proc_run('php','include/cron.php'); - } - exit; - } - } - - - $a->set_baseurl(get_config('system','url')); load_hooks(); diff --git a/include/cronhooks.php b/include/cronhooks.php index 8c70008e45..71cb0fb7b2 100644 --- a/include/cronhooks.php +++ b/include/cronhooks.php @@ -24,15 +24,33 @@ function cronhooks_run(&$argv, &$argc){ load_config('config'); load_config('system'); - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $maxsysload = intval(get_config('system','maxloadavg')); + if($maxsysload < 1) + $maxsysload = 50; - $load = current_load(); - if($load) { - if(intval($load) > $maxsysload) { - logger('system: load ' . $load . ' too high. Cronhooks deferred to next scheduled run.'); - return; + $load = current_load(); + if($load) { + if(intval($load) > $maxsysload) { + logger('system: load ' . $load . ' too high. Cronhooks deferred to next scheduled run.'); + return; + } + } + + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, 'cronhooks'); + if($pidfile->is_already_running()) { + logger("cronhooks: Already running"); + if ($pidfile->running_time() > 19*60) { + $pidfile->kill(); + logger("cronhooks: killed stale process"); + // Calling a new instance + proc_run('php','include/cronhooks.php'); + } + exit; + } } } @@ -50,21 +68,6 @@ function cronhooks_run(&$argv, &$argc){ } } - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'cronhooks'); - if($pidfile->is_already_running()) { - logger("cronhooks: Already running"); - if ($pidfile->running_time() > 19*60) { - $pidfile->kill(); - logger("cronhooks: killed stale process"); - // Calling a new instance - proc_run('php','include/cronhooks.php'); - } - exit; - } - } - $a->set_baseurl(get_config('system','url')); load_hooks(); diff --git a/include/discover_poco.php b/include/discover_poco.php index a8f670334b..550c9897be 100644 --- a/include/discover_poco.php +++ b/include/discover_poco.php @@ -25,15 +25,18 @@ function discover_poco_run(&$argv, &$argc){ load_config('config'); load_config('system'); - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $maxsysload = intval(get_config('system','maxloadavg')); + if($maxsysload < 1) + $maxsysload = 50; - $load = current_load(); - if($load) { - if(intval($load) > $maxsysload) { - logger('system: load ' . $load . ' too high. discover_poco deferred to next scheduled run.'); - return; + $load = current_load(); + if($load) { + if(intval($load) > $maxsysload) { + logger('system: load '.$load.' too high. discover_poco deferred to next scheduled run.'); + return; + } } } @@ -50,19 +53,22 @@ function discover_poco_run(&$argv, &$argc){ } else die("Unknown or missing parameter ".$argv[1]."\n"); - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'discover_poco'.$mode.urlencode($search)); - if($pidfile->is_already_running()) { - logger("discover_poco: Already running"); - if ($pidfile->running_time() > 19*60) { - $pidfile->kill(); - logger("discover_poco: killed stale process"); - // Calling a new instance - if ($mode == 0) - proc_run('php','include/discover_poco.php'); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, 'discover_poco'.$mode.urlencode($search)); + if($pidfile->is_already_running()) { + logger("discover_poco: Already running"); + if ($pidfile->running_time() > 19*60) { + $pidfile->kill(); + logger("discover_poco: killed stale process"); + // Calling a new instance + if ($mode == 0) + proc_run('php','include/discover_poco.php'); + } + exit; } - exit; } } diff --git a/include/onepoll.php b/include/onepoll.php index 6fb191f73d..8b91070dcc 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -60,16 +60,19 @@ function onepoll_run(&$argv, &$argc){ return; } - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'onepoll'.$contact_id); - if ($pidfile->is_already_running()) { - logger("onepoll: Already running for contact ".$contact_id); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("killed stale process"); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, 'onepoll'.$contact_id); + if ($pidfile->is_already_running()) { + logger("onepoll: Already running for contact ".$contact_id); + if ($pidfile->running_time() > 9*60) { + $pidfile->kill(); + logger("killed stale process"); + } + exit; } - exit; } } diff --git a/include/ostatus.php b/include/ostatus.php index 54b70e6d6c..ac13ce6bc8 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -954,16 +954,21 @@ function ostatus_completion($conversation_url, $uid, $item = array(), $self = "" if (isset($single_conv->actor->url)) $actor = $single_conv->actor->url; - $contact = q("SELECT `id`, `rel` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", $uid, normalise_link($actor), NETWORK_STATUSNET); - if (count($contact)) { + if (!$contact) + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'", + $uid, $actor, normalise_link($actor), NETWORK_STATUSNET); + + if ($contact) { logger("Found contact for url ".$actor, LOGGER_DEBUG); $contact_id = $contact[0]["id"]; + $network = $contact[0]["network"]; $not_following = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); } else { - logger("No contact found for url ".$actor, LOGGER_DEBUG); + logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG); // Adding a global contact /// @TODO Use this data for the post @@ -972,18 +977,19 @@ function ostatus_completion($conversation_url, $uid, $item = array(), $self = "" logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); $contact_id = $parent["contact-id"]; + $network = NETWORK_OSTATUS; $not_following = true; } // Do we only want to import threads that were started by our contacts? if ($not_following AND $new_parent AND get_config('system','ostatus_full_threads')) { - logger("Don't import uri ".$first_id." because we don't follow the person ".$actor, LOGGER_DEBUG); + logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG); continue; } $arr = array(); - $arr["network"] = NETWORK_OSTATUS; + $arr["network"] = $network; $arr["uri"] = $single_conv->id; $arr["plink"] = $plink; $arr["uid"] = $uid; diff --git a/include/pubsubpublish.php b/include/pubsubpublish.php index 0ac50aaaa7..e7a55f5f09 100644 --- a/include/pubsubpublish.php +++ b/include/pubsubpublish.php @@ -79,18 +79,21 @@ function pubsubpublish_run(&$argv, &$argc){ load_config('config'); load_config('system'); - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'pubsubpublish'); - if($pidfile->is_already_running()) { - logger("Already running"); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("killed stale process"); - // Calling a new instance - proc_run('php',"include/pubsubpublish.php"); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, 'pubsubpublish'); + if($pidfile->is_already_running()) { + logger("Already running"); + if ($pidfile->running_time() > 9*60) { + $pidfile->kill(); + logger("killed stale process"); + // Calling a new instance + proc_run('php',"include/pubsubpublish.php"); + } + return; } - return; } } diff --git a/include/queue.php b/include/queue.php index 1525ca3abf..157fc88d94 100644 --- a/include/queue.php +++ b/include/queue.php @@ -28,18 +28,21 @@ function queue_run(&$argv, &$argc){ load_config('config'); load_config('system'); - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'queue'); - if($pidfile->is_already_running()) { - logger("queue: Already running"); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("queue: killed stale process"); - // Calling a new instance - proc_run('php',"include/queue.php"); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, 'queue'); + if($pidfile->is_already_running()) { + logger("queue: Already running"); + if ($pidfile->running_time() > 9*60) { + $pidfile->kill(); + logger("queue: killed stale process"); + // Calling a new instance + proc_run('php',"include/queue.php"); + } + return; } - return; } } diff --git a/include/update_gcontact.php b/include/update_gcontact.php index b5ea30a0a4..b7bf25aa24 100644 --- a/include/update_gcontact.php +++ b/include/update_gcontact.php @@ -37,16 +37,19 @@ function update_gcontact_run(&$argv, &$argc){ return; } - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'update_gcontact'.$contact_id); - if ($pidfile->is_already_running()) { - logger("update_gcontact: Already running for contact ".$contact_id); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("killed stale process"); + // Don't check this stuff if the function is called by the poller + if (App::callstack() != "poller_run") { + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, 'update_gcontact'.$contact_id); + if ($pidfile->is_already_running()) { + logger("update_gcontact: Already running for contact ".$contact_id); + if ($pidfile->running_time() > 9*60) { + $pidfile->kill(); + logger("killed stale process"); + } + exit; } - exit; } } From beb2346cfc8e3aa57ed0203e35034241e814b61a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 8 Mar 2016 20:28:09 +0100 Subject: [PATCH 174/273] The function to check for maxload and the lockfile is centralized --- boot.php | 37 ++++++++++++++++++++++++++++++++++++- include/cron.php | 31 ++++--------------------------- include/cronhooks.php | 31 ++++--------------------------- include/delivery.php | 13 ++----------- include/discover_poco.php | 37 ++++++------------------------------- include/onepoll.php | 18 +++--------------- include/poller.php | 13 ++----------- include/pubsubpublish.php | 20 +++----------------- include/queue.php | 20 +++----------------- include/update_gcontact.php | 18 +++--------------- 10 files changed, 66 insertions(+), 172 deletions(-) diff --git a/boot.php b/boot.php index 5cc3499311..fe4e5a2757 100644 --- a/boot.php +++ b/boot.php @@ -30,7 +30,7 @@ require_once('include/cache.php'); require_once('library/Mobile_Detect/Mobile_Detect.php'); require_once('include/features.php'); require_once('include/identity.php'); - +require_once('include/pidfile.php'); require_once('update.php'); require_once('include/dbstructure.php'); @@ -1098,6 +1098,41 @@ class App { return($this->is_friendica_app); } + function maxload_reached() { + + $maxsysload = intval(get_config('system', 'maxloadavg')); + if ($maxsysload < 1) + $maxsysload = 50; + + $load = current_load(); + if ($load) { + if (intval($load) > $maxsysload) { + logger('system: load '.$load.' too high.'); + return true; + } + } + return false; + } + + function is_already_running($task, $taskname, $timeout = 540) { + + $lockpath = get_lockpath(); + if ($lockpath != '') { + $pidfile = new pidfile($lockpath, $taskname); + if ($pidfile->is_already_running()) { + logger("Already running"); + if ($pidfile->running_time() > $timeout) { + $pidfile->kill(); + logger("killed stale process"); + // Calling a new instance + if ($task != "") + proc_run('php', $task); + } + return true; + } + } + return false; + } } /** diff --git a/include/cron.php b/include/cron.php index 6143281710..1c3297c932 100644 --- a/include/cron.php +++ b/include/cron.php @@ -34,7 +34,6 @@ function cron_run(&$argv, &$argc){ require_once('include/Contact.php'); require_once('include/email.php'); require_once('include/socgraph.php'); - require_once('include/pidfile.php'); require_once('mod/nodeinfo.php'); load_config('config'); @@ -42,32 +41,10 @@ function cron_run(&$argv, &$argc){ // Don't check this stuff if the function is called by the poller if (App::callstack() != "poller_run") { - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; - - $load = current_load(); - if($load) { - if(intval($load) > $maxsysload) { - logger('system: load '.$load.' too high. cron deferred to next scheduled run.'); - return; - } - } - - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'cron'); - if($pidfile->is_already_running()) { - logger("cron: Already running"); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("cron: killed stale process"); - // Calling a new instance - proc_run('php','include/cron.php'); - } - exit; - } - } + if (App::maxload_reached()) + return; + if (App::is_already_running('include/cron.php', 'cron', 540)) + return; } $last = get_config('system','last_cron'); diff --git a/include/cronhooks.php b/include/cronhooks.php index 71cb0fb7b2..22812fb864 100644 --- a/include/cronhooks.php +++ b/include/cronhooks.php @@ -19,39 +19,16 @@ function cronhooks_run(&$argv, &$argc){ require_once('include/session.php'); require_once('include/datetime.php'); - require_once('include/pidfile.php'); load_config('config'); load_config('system'); // Don't check this stuff if the function is called by the poller if (App::callstack() != "poller_run") { - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; - - $load = current_load(); - if($load) { - if(intval($load) > $maxsysload) { - logger('system: load ' . $load . ' too high. Cronhooks deferred to next scheduled run.'); - return; - } - } - - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'cronhooks'); - if($pidfile->is_already_running()) { - logger("cronhooks: Already running"); - if ($pidfile->running_time() > 19*60) { - $pidfile->kill(); - logger("cronhooks: killed stale process"); - // Calling a new instance - proc_run('php','include/cronhooks.php'); - } - exit; - } - } + if (App::maxload_reached()) + return; + if (App::is_already_running('include/cronhooks.php', 'cronhooks', 1140)) + return; } $last = get_config('system','last_cronhook'); diff --git a/include/delivery.php b/include/delivery.php index 021ceb9968..e5ca0946b3 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -57,17 +57,8 @@ function delivery_run(&$argv, &$argc){ continue; } - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; - - $load = current_load(); - if($load) { - if(intval($load) > $maxsysload) { - logger('system: load ' . $load . ' too high. Delivery deferred to next queue run.'); - return; - } - } + if (App::maxload_reached()) + return; // It's ours to deliver. Remove it from the queue. diff --git a/include/discover_poco.php b/include/discover_poco.php index 550c9897be..8ba2bb2365 100644 --- a/include/discover_poco.php +++ b/include/discover_poco.php @@ -20,25 +20,14 @@ function discover_poco_run(&$argv, &$argc){ require_once('include/session.php'); require_once('include/datetime.php'); - require_once('include/pidfile.php'); load_config('config'); load_config('system'); // Don't check this stuff if the function is called by the poller - if (App::callstack() != "poller_run") { - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; - - $load = current_load(); - if($load) { - if(intval($load) > $maxsysload) { - logger('system: load '.$load.' too high. discover_poco deferred to next scheduled run.'); - return; - } - } - } + if (App::callstack() != "poller_run") + if (App::maxload_reached()) + return; if(($argc > 2) && ($argv[1] == "dirsearch")) { $search = urldecode($argv[2]); @@ -54,23 +43,9 @@ function discover_poco_run(&$argv, &$argc){ die("Unknown or missing parameter ".$argv[1]."\n"); // Don't check this stuff if the function is called by the poller - if (App::callstack() != "poller_run") { - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'discover_poco'.$mode.urlencode($search)); - if($pidfile->is_already_running()) { - logger("discover_poco: Already running"); - if ($pidfile->running_time() > 19*60) { - $pidfile->kill(); - logger("discover_poco: killed stale process"); - // Calling a new instance - if ($mode == 0) - proc_run('php','include/discover_poco.php'); - } - exit; - } - } - } + if (App::callstack() != "poller_run") + if (App::is_already_running('include/discover_poco.php', 'discover_poco'.$mode.urlencode($search), 1140)) + return; $a->set_baseurl(get_config('system','url')); diff --git a/include/onepoll.php b/include/onepoll.php index 8b91070dcc..4d270f6135 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -31,7 +31,6 @@ function onepoll_run(&$argv, &$argc){ require_once('include/Contact.php'); require_once('include/email.php'); require_once('include/socgraph.php'); - require_once('include/pidfile.php'); require_once('include/queue_fn.php'); load_config('config'); @@ -61,20 +60,9 @@ function onepoll_run(&$argv, &$argc){ } // Don't check this stuff if the function is called by the poller - if (App::callstack() != "poller_run") { - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'onepoll'.$contact_id); - if ($pidfile->is_already_running()) { - logger("onepoll: Already running for contact ".$contact_id); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("killed stale process"); - } - exit; - } - } - } + if (App::callstack() != "poller_run") + if (App::is_already_running('', 'onepoll'.$contact_id, 540)) + return; $d = datetime_convert(); diff --git a/include/poller.php b/include/poller.php index 755862eb6b..7ffd47aa68 100644 --- a/include/poller.php +++ b/include/poller.php @@ -29,17 +29,8 @@ function poller_run(&$argv, &$argc){ if (poller_max_connections_reached()) return; - $load = current_load(); - if($load) { - $maxsysload = intval(get_config('system','maxloadavg')); - if($maxsysload < 1) - $maxsysload = 50; - - if(intval($load) > $maxsysload) { - logger('system: load ' . $load . ' too high. poller deferred to next scheduled run.'); - return; - } - } + if (App::maxload_reached()) + return; // Checking the number of workers if (poller_too_much_workers(1)) { diff --git a/include/pubsubpublish.php b/include/pubsubpublish.php index e7a55f5f09..b438b36e6b 100644 --- a/include/pubsubpublish.php +++ b/include/pubsubpublish.php @@ -74,28 +74,14 @@ function pubsubpublish_run(&$argv, &$argc){ }; require_once('include/items.php'); - require_once('include/pidfile.php'); load_config('config'); load_config('system'); // Don't check this stuff if the function is called by the poller - if (App::callstack() != "poller_run") { - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'pubsubpublish'); - if($pidfile->is_already_running()) { - logger("Already running"); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("killed stale process"); - // Calling a new instance - proc_run('php',"include/pubsubpublish.php"); - } - return; - } - } - } + if (App::callstack() != "poller_run") + if (App::is_already_running("include/pubsubpublish.php", 'pubsubpublish', 540)) + return; $a->set_baseurl(get_config('system','url')); diff --git a/include/queue.php b/include/queue.php index 157fc88d94..1222199c70 100644 --- a/include/queue.php +++ b/include/queue.php @@ -22,29 +22,15 @@ function queue_run(&$argv, &$argc){ require_once("include/datetime.php"); require_once('include/items.php'); require_once('include/bbcode.php'); - require_once('include/pidfile.php'); require_once('include/socgraph.php'); load_config('config'); load_config('system'); // Don't check this stuff if the function is called by the poller - if (App::callstack() != "poller_run") { - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'queue'); - if($pidfile->is_already_running()) { - logger("queue: Already running"); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("queue: killed stale process"); - // Calling a new instance - proc_run('php',"include/queue.php"); - } - return; - } - } - } + if (App::callstack() != "poller_run") + if (App::is_already_running('include/queue.php', 'queue', 540)) + return; $a->set_baseurl(get_config('system','url')); diff --git a/include/update_gcontact.php b/include/update_gcontact.php index b7bf25aa24..25c11806a2 100644 --- a/include/update_gcontact.php +++ b/include/update_gcontact.php @@ -16,7 +16,6 @@ function update_gcontact_run(&$argv, &$argc){ unset($db_host, $db_user, $db_pass, $db_data); }; - require_once('include/pidfile.php'); require_once('include/Scrape.php'); require_once("include/socgraph.php"); @@ -38,20 +37,9 @@ function update_gcontact_run(&$argv, &$argc){ } // Don't check this stuff if the function is called by the poller - if (App::callstack() != "poller_run") { - $lockpath = get_lockpath(); - if ($lockpath != '') { - $pidfile = new pidfile($lockpath, 'update_gcontact'.$contact_id); - if ($pidfile->is_already_running()) { - logger("update_gcontact: Already running for contact ".$contact_id); - if ($pidfile->running_time() > 9*60) { - $pidfile->kill(); - logger("killed stale process"); - } - exit; - } - } - } + if (App::callstack() != "poller_run") + if (App::is_already_running('', 'update_gcontact'.$contact_id, 540)) + return; $r = q("SELECT * FROM `gcontact` WHERE `id` = %d", intval($contact_id)); From 65d6d45f8cdb2e1f84fc0c345043598a221312de Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 8 Mar 2016 22:28:49 +0100 Subject: [PATCH 175/273] Changed parameter order --- boot.php | 2 +- include/cron.php | 2 +- include/cronhooks.php | 2 +- include/discover_poco.php | 2 +- include/onepoll.php | 2 +- include/pubsubpublish.php | 2 +- include/queue.php | 2 +- include/update_gcontact.php | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/boot.php b/boot.php index fe4e5a2757..4d57d21e62 100644 --- a/boot.php +++ b/boot.php @@ -1114,7 +1114,7 @@ class App { return false; } - function is_already_running($task, $taskname, $timeout = 540) { + function is_already_running($taskname, $task = "", $timeout = 540) { $lockpath = get_lockpath(); if ($lockpath != '') { diff --git a/include/cron.php b/include/cron.php index 1c3297c932..d68bd7f084 100644 --- a/include/cron.php +++ b/include/cron.php @@ -43,7 +43,7 @@ function cron_run(&$argv, &$argc){ if (App::callstack() != "poller_run") { if (App::maxload_reached()) return; - if (App::is_already_running('include/cron.php', 'cron', 540)) + if (App::is_already_running('cron', 'include/cron.php', 540)) return; } diff --git a/include/cronhooks.php b/include/cronhooks.php index 22812fb864..b6cf0e7237 100644 --- a/include/cronhooks.php +++ b/include/cronhooks.php @@ -27,7 +27,7 @@ function cronhooks_run(&$argv, &$argc){ if (App::callstack() != "poller_run") { if (App::maxload_reached()) return; - if (App::is_already_running('include/cronhooks.php', 'cronhooks', 1140)) + if (App::is_already_running('cronhooks', 'include/cronhooks.php', 1140)) return; } diff --git a/include/discover_poco.php b/include/discover_poco.php index 8ba2bb2365..0b468faea1 100644 --- a/include/discover_poco.php +++ b/include/discover_poco.php @@ -44,7 +44,7 @@ function discover_poco_run(&$argv, &$argc){ // Don't check this stuff if the function is called by the poller if (App::callstack() != "poller_run") - if (App::is_already_running('include/discover_poco.php', 'discover_poco'.$mode.urlencode($search), 1140)) + if (App::is_already_running('discover_poco'.$mode.urlencode($search), 'include/discover_poco.php', 1140)) return; $a->set_baseurl(get_config('system','url')); diff --git a/include/onepoll.php b/include/onepoll.php index 4d270f6135..eb1045de14 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -61,7 +61,7 @@ function onepoll_run(&$argv, &$argc){ // Don't check this stuff if the function is called by the poller if (App::callstack() != "poller_run") - if (App::is_already_running('', 'onepoll'.$contact_id, 540)) + if (App::is_already_running('onepoll'.$contact_id, '', 540)) return; $d = datetime_convert(); diff --git a/include/pubsubpublish.php b/include/pubsubpublish.php index b438b36e6b..4fbb505146 100644 --- a/include/pubsubpublish.php +++ b/include/pubsubpublish.php @@ -80,7 +80,7 @@ function pubsubpublish_run(&$argv, &$argc){ // Don't check this stuff if the function is called by the poller if (App::callstack() != "poller_run") - if (App::is_already_running("include/pubsubpublish.php", 'pubsubpublish', 540)) + if (App::is_already_running("pubsubpublish", "include/pubsubpublish.php", 540)) return; $a->set_baseurl(get_config('system','url')); diff --git a/include/queue.php b/include/queue.php index 1222199c70..183ce0f9cd 100644 --- a/include/queue.php +++ b/include/queue.php @@ -29,7 +29,7 @@ function queue_run(&$argv, &$argc){ // Don't check this stuff if the function is called by the poller if (App::callstack() != "poller_run") - if (App::is_already_running('include/queue.php', 'queue', 540)) + if (App::is_already_running('queue', 'include/queue.php', 540)) return; $a->set_baseurl(get_config('system','url')); diff --git a/include/update_gcontact.php b/include/update_gcontact.php index 25c11806a2..88e1817f0b 100644 --- a/include/update_gcontact.php +++ b/include/update_gcontact.php @@ -38,7 +38,7 @@ function update_gcontact_run(&$argv, &$argc){ // Don't check this stuff if the function is called by the poller if (App::callstack() != "poller_run") - if (App::is_already_running('', 'update_gcontact'.$contact_id, 540)) + if (App::is_already_running('update_gcontact'.$contact_id, '', 540)) return; $r = q("SELECT * FROM `gcontact` WHERE `id` = %d", intval($contact_id)); From c30b369a1a60af4635b8f0a6ae823303fa50e3da Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 8 Mar 2016 22:42:37 +0100 Subject: [PATCH 176/273] Added documentation --- boot.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/boot.php b/boot.php index 4d57d21e62..43f69fc115 100644 --- a/boot.php +++ b/boot.php @@ -1098,6 +1098,11 @@ class App { return($this->is_friendica_app); } + /** + * @brief Checks if the maximum load is reached + * + * @return bool Is the load reached? + */ function maxload_reached() { $maxsysload = intval(get_config('system', 'maxloadavg')); @@ -1114,6 +1119,15 @@ class App { return false; } + /** + * @brief Checks if the process is already running + * + * @param string $taskname The name of the task that will be used for the name of the lockfile + * @param string $task The path and name of the php script + * @param int $timeout The timeout after which a task should be killed + * + * @return bool Is the process running? + */ function is_already_running($taskname, $task = "", $timeout = 540) { $lockpath = get_lockpath(); From 32c66246c4fcddcab30e4850d5c6b62bdfac225a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 9 Mar 2016 19:30:04 +0100 Subject: [PATCH 177/273] Some fixes for the fetching of postings by using /p/ --- include/diaspora2.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 4c9d79912e..7aa0fc6989 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -550,12 +550,15 @@ class diaspora { return self::fetch_message($source_xml->root_guid, $server, ++$level); } + $author = ""; + // Fetch the author - for the old and the new Diaspora version if ($source_xml->post->status_message->diaspora_handle) $author = (string)$source_xml->post->status_message->diaspora_handle; - elseif ($source_xml->author) + elseif ($source_xml->author AND ($source_xml->getName() == "status_message")) $author = (string)$source_xml->author; + // If this isn't a "status_message" then quit if (!$author) return false; @@ -1391,22 +1394,24 @@ EOT; logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); $item_id = self::store_by_guid($guid, $server); - if (!$item_id) { - $server = "https://".substr($author, strpos($author, "@") + 1); - logger("2nd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item = self::store_by_guid($guid, $server); - } if (!$item_id) { $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1); - logger("3rd try: reshared message ".$guid." will be fetched from original server: ".$server); - $item = self::store_by_guid($guid, $server); + logger("2nd try: reshared message ".$guid." will be fetched from original server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } + + // Deactivated by now since there is a risk that someone could manipulate postings through this method +/* if (!$item_id) { + $server = "https://".substr($author, strpos($author, "@") + 1); + logger("3rd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item_id = self::store_by_guid($guid, $server); } if (!$item_id) { $server = "http://".substr($author, strpos($author, "@") + 1); logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item = self::store_by_guid($guid, $server); + $item_id = self::store_by_guid($guid, $server); } - +*/ if ($item_id) { $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, `author-name`, `author-link`, `author-avatar` From c0bd7a866d7dc0dc0ebc1e82226add912e39cffa Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 9 Mar 2016 21:53:30 +0100 Subject: [PATCH 178/273] Some small addition to the OStatus part. Better check if we follow the actor. --- include/ostatus.php | 86 +++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index ac13ce6bc8..0e79b32f89 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -714,6 +714,49 @@ function ostatus_fetch_conversation($self, $conversation_id = "") { return $base_url."/conversation/".$conversation_id; } +/** + * @brief Fetches actor details of a given actor and user id + * + * @param string $actor The actor url + * @param int $uid The user id + * + * @return array Array with actor details + */ +function ostatus_get_actor_details($actor, $uid) { + + $details = array(); + + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + $uid, normalise_link($actor), NETWORK_STATUSNET); + + if (!$contact) + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'", + $uid, $actor, normalise_link($actor), NETWORK_STATUSNET); + + if ($contact) { + logger("Found contact for url ".$actor, LOGGER_DEBUG); + $details["contact_id"] = $contact[0]["id"]; + $details["network"] = $contact[0]["network"]; + + $details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); + } else { + logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG); + + // Adding a global contact + /// @TODO Use this data for the post + $details["global_contact_id"] = get_contact($actor, 0); + + logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); + + $details["contact_id"] = $parent["contact-id"]; + $details["network"] = NETWORK_OSTATUS; + + $details["not_following"] = true; + } + + return $details; +} + function ostatus_completion($conversation_url, $uid, $item = array(), $self = "") { $a = get_app(); @@ -954,46 +997,20 @@ function ostatus_completion($conversation_url, $uid, $item = array(), $self = "" if (isset($single_conv->actor->url)) $actor = $single_conv->actor->url; - $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", - $uid, normalise_link($actor), NETWORK_STATUSNET); - - if (!$contact) - $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'", - $uid, $actor, normalise_link($actor), NETWORK_STATUSNET); - - if ($contact) { - logger("Found contact for url ".$actor, LOGGER_DEBUG); - $contact_id = $contact[0]["id"]; - $network = $contact[0]["network"]; - - $not_following = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); - } else { - logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG); - - // Adding a global contact - /// @TODO Use this data for the post - $global_contact_id = get_contact($actor, 0); - - logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); - - $contact_id = $parent["contact-id"]; - $network = NETWORK_OSTATUS; - - $not_following = true; - } + $details = ostatus_get_actor_details($actor, $uid); // Do we only want to import threads that were started by our contacts? - if ($not_following AND $new_parent AND get_config('system','ostatus_full_threads')) { + if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) { logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG); continue; } $arr = array(); - $arr["network"] = $network; + $arr["network"] = $details["network"]; $arr["uri"] = $single_conv->id; $arr["plink"] = $plink; $arr["uid"] = $uid; - $arr["contact-id"] = $contact_id; + $arr["contact-id"] = $details["contact_id"]; $arr["parent-uri"] = $parent_uri; $arr["created"] = $single_conv->published; $arr["edited"] = $single_conv->published; @@ -1119,6 +1136,15 @@ function ostatus_completion($conversation_url, $uid, $item = array(), $self = "" if (($item_stored < 0) AND (count($item) > 0)) { //$arr["app"] .= " (OStatus-NoConvFound)"; + + if (get_config('system','ostatus_full_threads')) { + $details = ostatus_get_actor_details($item["owner-link"], $uid); + if ($details["not_following"]) { + logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG); + return false; + } + } + $item_stored = item_store($item, true); if ($item_stored) { logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG); From df312402e46cc3cdc788c73d0c41874b6c71fcb3 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 9 Mar 2016 22:51:17 +0100 Subject: [PATCH 179/273] Bugfix: A parameter was missing for the OStatus author check --- include/ostatus.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 0e79b32f89..5ba9f0e83c 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -719,10 +719,11 @@ function ostatus_fetch_conversation($self, $conversation_id = "") { * * @param string $actor The actor url * @param int $uid The user id + * @param int $contact_id The default contact-id * * @return array Array with actor details */ -function ostatus_get_actor_details($actor, $uid) { +function ostatus_get_actor_details($actor, $uid, $contact_id) { $details = array(); @@ -748,7 +749,7 @@ function ostatus_get_actor_details($actor, $uid) { logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); - $details["contact_id"] = $parent["contact-id"]; + $details["contact_id"] = $contact_id; $details["network"] = NETWORK_OSTATUS; $details["not_following"] = true; @@ -997,7 +998,7 @@ function ostatus_completion($conversation_url, $uid, $item = array(), $self = "" if (isset($single_conv->actor->url)) $actor = $single_conv->actor->url; - $details = ostatus_get_actor_details($actor, $uid); + $details = ostatus_get_actor_details($actor, $uid, $parent["contact-id"]); // Do we only want to import threads that were started by our contacts? if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) { @@ -1138,7 +1139,7 @@ function ostatus_completion($conversation_url, $uid, $item = array(), $self = "" //$arr["app"] .= " (OStatus-NoConvFound)"; if (get_config('system','ostatus_full_threads')) { - $details = ostatus_get_actor_details($item["owner-link"], $uid); + $details = ostatus_get_actor_details($item["owner-link"], $uid, $item["contact-id"]); if ($details["not_following"]) { logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG); return false; From 2718c8150f614e6d5c76f91d46f4ebb28e440124 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 11 Mar 2016 20:32:22 +0100 Subject: [PATCH 180/273] The table optimisation level calculation is now using the index size as well --- include/cron.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/cron.php b/include/cron.php index d68bd7f084..db7d44be0b 100644 --- a/include/cron.php +++ b/include/cron.php @@ -357,7 +357,7 @@ function cron_clear_cache(&$a) { continue; // Calculate fragmentation - $fragmentation = $table["Data_free"] / $table["Data_length"]; + $fragmentation = $table["Data_free"] / ($table["Data_length"] + $table["Index_length"]); logger("Table ".$table["Name"]." - Fragmentation level: ".round($fragmentation * 100, 2), LOGGER_DEBUG); From 3189f0e23c234ca78396bcf5d44ac02775e491fa Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 11 Mar 2016 20:43:31 +0100 Subject: [PATCH 181/273] Another fix for "goaway". Hopefully now finale. --- boot.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot.php b/boot.php index 43f69fc115..a70c7bf616 100644 --- a/boot.php +++ b/boot.php @@ -1558,7 +1558,7 @@ function killme() { * @brief Redirect to another URL and terminate this process. */ function goaway($s) { - if (!strstr(normalise_link($s), normalise_link(App::get_baseurl()))) + if (!strstr(normalise_link($s), "http://")) $s = App::get_baseurl()."/".$s; header("Location: $s"); From b12dbe8831953cf244a66074dc3338baaae02b8a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 11 Mar 2016 23:28:11 +0100 Subject: [PATCH 182/273] The worker settings are now available in the admin settings --- mod/admin.php | 11 ++++++++++- view/templates/admin_site.tpl | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/mod/admin.php b/mod/admin.php index e7c4f51b66..57a004f51a 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -619,6 +619,9 @@ function admin_page_site_post(&$a) { $only_tag_search = ((x($_POST,'only_tag_search')) ? True : False); $rino = ((x($_POST,'rino')) ? intval($_POST['rino']) : 0); $embedly = ((x($_POST,'embedly')) ? notags(trim($_POST['embedly'])) : ''); + $worker = ((x($_POST,'worker')) ? True : False); + $worker_queues = ((x($_POST,'worker_queues')) ? intval($_POST['worker_queues']) : 4); + $worker_dont_fork = ((x($_POST,'worker_dont_fork')) ? True : False); if($a->get_path() != "") $diaspora_enabled = false; @@ -765,7 +768,9 @@ function admin_page_site_post(&$a) { set_config('system','proxy_disabled', $proxy_disabled); set_config('system','old_pager', $old_pager); set_config('system','only_tag_search', $only_tag_search); - + set_config('system','worker', $worker); + set_config('system','worker_queues', $worker_queues); + set_config('system','worker_dont_fork', $worker_dont_fork); if($rino==2 and !function_exists('mcrypt_create_iv')) { notice(t("RINO2 needs mcrypt php extension to work.")); @@ -992,6 +997,10 @@ function admin_page_site(&$a) { '$rino' => array('rino', t("RINO Encryption"), intval(get_config('system','rino_encrypt')), t("Encryption layer between nodes."), array("Disabled", "RINO1 (deprecated)", "RINO2")), '$embedly' => array('embedly', t("Embedly API key"), get_config('system','embedly'), t("<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for web pages. This is an optional parameter.")), + '$worker' => array('worker', t("Enable 'worker' background processing"), get_config('system','worker'), t("The worker background processing limits the number of parallel background jobs to a maximum number and respects the system load.")), + '$worker_queues' => array('worker_queues', t("Maximum number of parallel workers"), get_config('system','worker_queues'), t("On shared hosters set this to 2. On larger systems, values of 10 are great. Default value is 4.")), + '$worker_dont_fork' => array('worker_dont_fork', t("Don't use 'proc_open' with the worker"), get_config('system','worker_dont_fork'), t("Enable this if your system doesn't allow the use of 'proc_open'. This can happen on shared hosters. If this is enabled you should increase the frequency of poller calls in your crontab.")), + '$form_security_token' => get_form_security_token("admin_site") )); diff --git a/view/templates/admin_site.tpl b/view/templates/admin_site.tpl index 91957d016a..d43c6a8c4a 100644 --- a/view/templates/admin_site.tpl +++ b/view/templates/admin_site.tpl @@ -152,6 +152,11 @@ {{include file="field_input.tpl" field=$max_comments}} {{include file="field_checkbox.tpl" field=$proxy_disabled}} {{include file="field_checkbox.tpl" field=$old_pager}} + + {{include file="field_checkbox.tpl" field=$worker}} + {{include file="field_input.tpl" field=$worker_queues}} + {{include file="field_checkbox.tpl" field=$worker_dont_fork}} + <div class="submit"><input type="submit" name="page_site" value="{{$submit|escape:'html'}}" /></div> </form> From bc21fca34567ca0fb57eba6b2e2c38fb1b77462c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 11 Mar 2016 23:44:46 +0100 Subject: [PATCH 183/273] Remove the setting description from the documentation since it isn't a hidden one anymore. --- doc/htconfig.md | 3 --- mod/admin.php | 1 + view/templates/admin_site.tpl | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/htconfig.md b/doc/htconfig.md index f9c92bfa08..a36e0bef22 100644 --- a/doc/htconfig.md +++ b/doc/htconfig.md @@ -64,9 +64,6 @@ line to your .htconfig.php: * throttle_limit_week - Maximum number of posts that a user can send per week with the API. * throttle_limit_month - Maximum number of posts that a user can send per month with the API. * wall-to-wall_share (Boolean) - Displays forwarded posts like "wall-to-wall" posts. -* worker (Boolean) - (Experimental) Use the worker system instead of calling several background processes. Reduces the overall load and speeds up item delivery. -* worker_dont_fork (Boolean) - if enabled, the workers are only called from the poller process. Useful on systems that permit the use of "proc_open". -* worker_queues - Number of parallel workers. Default value is 10 queues. * xrd_timeout - Timeout for fetching the XRD links. Default value is 20 seconds. ## service_class ## diff --git a/mod/admin.php b/mod/admin.php index 57a004f51a..28c8ed15c2 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -909,6 +909,7 @@ function admin_page_site(&$a) { '$advanced' => t('Advanced'), '$portable_contacts' => t('Auto Discovered Contact Directory'), '$performance' => t('Performance'), + '$worker_title' => t('Worker'), '$relocate'=> t('Relocate - WARNING: advanced function. Could make this server unreachable.'), '$baseurl' => $a->get_baseurl(true), // name, label, value, help string, extra data... diff --git a/view/templates/admin_site.tpl b/view/templates/admin_site.tpl index d43c6a8c4a..f22319b695 100644 --- a/view/templates/admin_site.tpl +++ b/view/templates/admin_site.tpl @@ -152,11 +152,12 @@ {{include file="field_input.tpl" field=$max_comments}} {{include file="field_checkbox.tpl" field=$proxy_disabled}} {{include file="field_checkbox.tpl" field=$old_pager}} + <div class="submit"><input type="submit" name="page_site" value="{{$submit|escape:'html'}}" /></div> + <h3>{{$worker_title}}</h3> {{include file="field_checkbox.tpl" field=$worker}} {{include file="field_input.tpl" field=$worker_queues}} {{include file="field_checkbox.tpl" field=$worker_dont_fork}} - <div class="submit"><input type="submit" name="page_site" value="{{$submit|escape:'html'}}" /></div> </form> From 9d52a89c513438cf9c780d10f532eab201b1c2c3 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Sat, 12 Mar 2016 07:36:44 +0100 Subject: [PATCH 184/273] regenerated messages.po file for picking up the workers --- util/messages.po | 2187 ++++++++++++++++++++++++---------------------- 1 file changed, 1124 insertions(+), 1063 deletions(-) diff --git a/util/messages.po b/util/messages.po index 46a9913f45..df1dedd68a 100644 --- a/util/messages.po +++ b/util/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-01-24 06:49+0100\n" +"POT-Creation-Date: 2016-03-12 07:34+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -18,15 +18,15 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" -#: mod/contacts.php:50 include/identity.php:395 +#: mod/contacts.php:50 include/identity.php:396 msgid "Network:" msgstr "" -#: mod/contacts.php:51 mod/contacts.php:961 mod/videos.php:37 +#: mod/contacts.php:51 mod/contacts.php:947 mod/videos.php:37 #: mod/viewcontacts.php:105 mod/dirfind.php:214 mod/network.php:598 #: mod/allfriends.php:77 mod/match.php:82 mod/directory.php:172 #: mod/common.php:123 mod/suggest.php:95 mod/photos.php:41 -#: include/identity.php:298 +#: include/identity.php:299 msgid "Forum" msgstr "" @@ -37,7 +37,7 @@ msgid_plural "%d contacts edited." msgstr[0] "" msgstr[1] "" -#: mod/contacts.php:159 mod/contacts.php:383 +#: mod/contacts.php:159 mod/contacts.php:368 msgid "Could not access contact record." msgstr "" @@ -49,150 +49,150 @@ msgstr "" msgid "Contact updated." msgstr "" -#: mod/contacts.php:208 mod/dfrn_request.php:575 +#: mod/contacts.php:208 mod/dfrn_request.php:573 msgid "Failed to update contact record." msgstr "" -#: mod/contacts.php:365 mod/manage.php:96 mod/display.php:509 +#: mod/contacts.php:350 mod/manage.php:96 mod/display.php:513 #: mod/profile_photo.php:19 mod/profile_photo.php:175 mod/profile_photo.php:186 #: mod/profile_photo.php:199 mod/ostatus_subscribe.php:9 mod/follow.php:11 -#: mod/follow.php:73 mod/follow.php:155 mod/item.php:180 mod/item.php:192 +#: mod/follow.php:73 mod/follow.php:155 mod/item.php:183 mod/item.php:195 #: mod/group.php:19 mod/dfrn_confirm.php:55 mod/fsuggest.php:78 #: mod/wall_upload.php:77 mod/wall_upload.php:80 mod/viewcontacts.php:40 #: mod/notifications.php:69 mod/message.php:45 mod/message.php:181 -#: mod/crepair.php:117 mod/dirfind.php:11 mod/nogroup.php:25 mod/network.php:4 +#: mod/crepair.php:100 mod/dirfind.php:11 mod/nogroup.php:25 mod/network.php:4 #: mod/allfriends.php:12 mod/events.php:165 mod/wallmessage.php:9 #: mod/wallmessage.php:33 mod/wallmessage.php:79 mod/wallmessage.php:103 #: mod/wall_attach.php:67 mod/wall_attach.php:70 mod/settings.php:20 -#: mod/settings.php:126 mod/settings.php:646 mod/register.php:42 +#: mod/settings.php:126 mod/settings.php:647 mod/register.php:42 #: mod/delegate.php:12 mod/common.php:18 mod/mood.php:114 mod/suggest.php:58 -#: mod/profiles.php:165 mod/profiles.php:615 mod/editpost.php:10 mod/api.php:26 +#: mod/profiles.php:165 mod/profiles.php:593 mod/editpost.php:10 mod/api.php:26 #: mod/api.php:31 mod/notes.php:22 mod/poke.php:149 mod/repair_ostatus.php:9 -#: mod/invite.php:15 mod/invite.php:101 mod/photos.php:171 mod/photos.php:1105 +#: mod/invite.php:15 mod/invite.php:101 mod/photos.php:171 mod/photos.php:1091 #: mod/regmod.php:110 mod/uimport.php:23 mod/attach.php:33 -#: include/items.php:5096 index.php:383 +#: include/items.php:2002 index.php:384 msgid "Permission denied." msgstr "" -#: mod/contacts.php:404 +#: mod/contacts.php:389 msgid "Contact has been blocked" msgstr "" -#: mod/contacts.php:404 +#: mod/contacts.php:389 msgid "Contact has been unblocked" msgstr "" -#: mod/contacts.php:415 +#: mod/contacts.php:400 msgid "Contact has been ignored" msgstr "" -#: mod/contacts.php:415 +#: mod/contacts.php:400 msgid "Contact has been unignored" msgstr "" -#: mod/contacts.php:427 +#: mod/contacts.php:412 msgid "Contact has been archived" msgstr "" -#: mod/contacts.php:427 +#: mod/contacts.php:412 msgid "Contact has been unarchived" msgstr "" -#: mod/contacts.php:454 mod/contacts.php:802 +#: mod/contacts.php:439 mod/contacts.php:794 msgid "Do you really want to delete this contact?" msgstr "" -#: mod/contacts.php:456 mod/follow.php:110 mod/message.php:216 -#: mod/settings.php:1103 mod/settings.php:1109 mod/settings.php:1117 -#: mod/settings.php:1121 mod/settings.php:1126 mod/settings.php:1132 -#: mod/settings.php:1138 mod/settings.php:1144 mod/settings.php:1170 -#: mod/settings.php:1171 mod/settings.php:1172 mod/settings.php:1173 -#: mod/settings.php:1174 mod/dfrn_request.php:857 mod/register.php:238 -#: mod/suggest.php:29 mod/profiles.php:658 mod/profiles.php:661 -#: mod/profiles.php:687 mod/api.php:105 include/items.php:4928 +#: mod/contacts.php:441 mod/follow.php:110 mod/message.php:216 +#: mod/settings.php:1107 mod/settings.php:1113 mod/settings.php:1121 +#: mod/settings.php:1125 mod/settings.php:1130 mod/settings.php:1136 +#: mod/settings.php:1142 mod/settings.php:1148 mod/settings.php:1174 +#: mod/settings.php:1175 mod/settings.php:1176 mod/settings.php:1177 +#: mod/settings.php:1178 mod/dfrn_request.php:855 mod/register.php:238 +#: mod/suggest.php:29 mod/profiles.php:636 mod/profiles.php:639 +#: mod/profiles.php:665 mod/api.php:105 include/items.php:1834 msgid "Yes" msgstr "" -#: mod/contacts.php:459 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121 +#: mod/contacts.php:444 mod/tagrm.php:11 mod/tagrm.php:94 mod/follow.php:121 #: mod/videos.php:131 mod/message.php:219 mod/fbrowser.php:93 -#: mod/fbrowser.php:128 mod/settings.php:660 mod/settings.php:686 -#: mod/dfrn_request.php:871 mod/suggest.php:32 mod/editpost.php:148 +#: mod/fbrowser.php:128 mod/settings.php:661 mod/settings.php:687 +#: mod/dfrn_request.php:869 mod/suggest.php:32 mod/editpost.php:148 #: mod/photos.php:247 mod/photos.php:336 include/conversation.php:1220 -#: include/items.php:4931 +#: include/items.php:1837 msgid "Cancel" msgstr "" -#: mod/contacts.php:471 +#: mod/contacts.php:456 msgid "Contact has been removed." msgstr "" -#: mod/contacts.php:512 +#: mod/contacts.php:497 #, php-format msgid "You are mutual friends with %s" msgstr "" -#: mod/contacts.php:516 +#: mod/contacts.php:501 #, php-format msgid "You are sharing with %s" msgstr "" -#: mod/contacts.php:521 +#: mod/contacts.php:506 #, php-format msgid "%s is sharing with you" msgstr "" -#: mod/contacts.php:541 +#: mod/contacts.php:526 msgid "Private communications are not available for this contact." msgstr "" -#: mod/contacts.php:544 mod/admin.php:822 +#: mod/contacts.php:529 mod/admin.php:838 msgid "Never" msgstr "" -#: mod/contacts.php:548 +#: mod/contacts.php:533 msgid "(Update was successful)" msgstr "" -#: mod/contacts.php:548 +#: mod/contacts.php:533 msgid "(Update was not successful)" msgstr "" -#: mod/contacts.php:550 +#: mod/contacts.php:535 mod/contacts.php:972 msgid "Suggest friends" msgstr "" -#: mod/contacts.php:554 +#: mod/contacts.php:539 #, php-format msgid "Network type: %s" msgstr "" -#: mod/contacts.php:567 +#: mod/contacts.php:552 msgid "Communications lost with this contact!" msgstr "" -#: mod/contacts.php:570 +#: mod/contacts.php:555 msgid "Fetch further information for feeds" msgstr "" -#: mod/contacts.php:571 mod/admin.php:831 +#: mod/contacts.php:556 mod/admin.php:847 msgid "Disabled" msgstr "" -#: mod/contacts.php:571 +#: mod/contacts.php:556 msgid "Fetch information" msgstr "" -#: mod/contacts.php:571 +#: mod/contacts.php:556 msgid "Fetch information and keywords" msgstr "" -#: mod/contacts.php:587 mod/manage.php:143 mod/fsuggest.php:107 -#: mod/message.php:342 mod/message.php:525 mod/crepair.php:196 +#: mod/contacts.php:575 mod/manage.php:143 mod/fsuggest.php:107 +#: mod/message.php:342 mod/message.php:525 mod/crepair.php:179 #: mod/events.php:574 mod/content.php:712 mod/install.php:261 -#: mod/install.php:299 mod/mood.php:137 mod/profiles.php:696 -#: mod/localtime.php:45 mod/poke.php:198 mod/invite.php:140 mod/photos.php:1137 -#: mod/photos.php:1261 mod/photos.php:1579 mod/photos.php:1630 -#: mod/photos.php:1678 mod/photos.php:1766 object/Item.php:710 +#: mod/install.php:299 mod/mood.php:137 mod/profiles.php:674 +#: mod/localtime.php:45 mod/poke.php:198 mod/invite.php:140 mod/photos.php:1123 +#: mod/photos.php:1247 mod/photos.php:1565 mod/photos.php:1616 +#: mod/photos.php:1664 mod/photos.php:1752 object/Item.php:710 #: view/theme/cleanzero/config.php:80 view/theme/dispy/config.php:70 #: view/theme/quattro/config.php:64 view/theme/diabook/config.php:148 #: view/theme/diabook/theme.php:633 view/theme/vier/config.php:107 @@ -200,306 +200,316 @@ msgstr "" msgid "Submit" msgstr "" -#: mod/contacts.php:588 +#: mod/contacts.php:576 msgid "Profile Visibility" msgstr "" -#: mod/contacts.php:589 +#: mod/contacts.php:577 #, php-format msgid "" "Please choose the profile you would like to display to %s when viewing your " "profile securely." msgstr "" -#: mod/contacts.php:590 +#: mod/contacts.php:578 msgid "Contact Information / Notes" msgstr "" -#: mod/contacts.php:591 +#: mod/contacts.php:579 msgid "Edit contact notes" msgstr "" -#: mod/contacts.php:596 mod/contacts.php:952 mod/viewcontacts.php:97 +#: mod/contacts.php:584 mod/contacts.php:938 mod/viewcontacts.php:97 #: mod/nogroup.php:41 #, php-format msgid "Visit %s's profile [%s]" msgstr "" -#: mod/contacts.php:597 +#: mod/contacts.php:585 msgid "Block/Unblock contact" msgstr "" -#: mod/contacts.php:598 +#: mod/contacts.php:586 msgid "Ignore contact" msgstr "" -#: mod/contacts.php:599 +#: mod/contacts.php:587 msgid "Repair URL settings" msgstr "" -#: mod/contacts.php:600 +#: mod/contacts.php:588 msgid "View conversations" msgstr "" -#: mod/contacts.php:602 -msgid "Delete contact" -msgstr "" - -#: mod/contacts.php:606 +#: mod/contacts.php:594 msgid "Last update:" msgstr "" -#: mod/contacts.php:608 +#: mod/contacts.php:596 msgid "Update public posts" msgstr "" -#: mod/contacts.php:610 +#: mod/contacts.php:598 mod/contacts.php:982 msgid "Update now" msgstr "" -#: mod/contacts.php:612 mod/follow.php:103 mod/dirfind.php:196 +#: mod/contacts.php:600 mod/follow.php:103 mod/dirfind.php:196 #: mod/allfriends.php:65 mod/match.php:71 mod/suggest.php:82 -#: include/contact_widgets.php:32 include/Contact.php:297 +#: include/contact_widgets.php:32 include/Contact.php:299 #: include/conversation.php:924 msgid "Connect/Follow" msgstr "" -#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865 -#: mod/admin.php:1312 +#: mod/contacts.php:603 mod/contacts.php:798 mod/contacts.php:991 +#: mod/admin.php:1334 msgid "Unblock" msgstr "" -#: mod/contacts.php:615 mod/contacts.php:806 mod/contacts.php:865 -#: mod/admin.php:1311 +#: mod/contacts.php:603 mod/contacts.php:798 mod/contacts.php:991 +#: mod/admin.php:1333 msgid "Block" msgstr "" -#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872 +#: mod/contacts.php:604 mod/contacts.php:799 mod/contacts.php:999 msgid "Unignore" msgstr "" -#: mod/contacts.php:616 mod/contacts.php:807 mod/contacts.php:872 +#: mod/contacts.php:604 mod/contacts.php:799 mod/contacts.php:999 #: mod/notifications.php:54 mod/notifications.php:179 mod/notifications.php:259 msgid "Ignore" msgstr "" -#: mod/contacts.php:619 +#: mod/contacts.php:607 msgid "Currently blocked" msgstr "" -#: mod/contacts.php:620 +#: mod/contacts.php:608 msgid "Currently ignored" msgstr "" -#: mod/contacts.php:621 +#: mod/contacts.php:609 msgid "Currently archived" msgstr "" -#: mod/contacts.php:622 mod/notifications.php:172 mod/notifications.php:251 +#: mod/contacts.php:610 mod/notifications.php:172 mod/notifications.php:251 msgid "Hide this contact from others" msgstr "" -#: mod/contacts.php:622 +#: mod/contacts.php:610 msgid "" "Replies/likes to your public posts <strong>may</strong> still be visible" msgstr "" -#: mod/contacts.php:623 +#: mod/contacts.php:611 msgid "Notification for new posts" msgstr "" -#: mod/contacts.php:623 +#: mod/contacts.php:611 msgid "Send a notification of every new post of this contact" msgstr "" -#: mod/contacts.php:626 +#: mod/contacts.php:614 msgid "Blacklisted keywords" msgstr "" -#: mod/contacts.php:626 +#: mod/contacts.php:614 msgid "" "Comma separated list of keywords that should not be converted to hashtags, " "when \"Fetch information and keywords\" is selected" msgstr "" -#: mod/contacts.php:633 mod/follow.php:126 mod/notifications.php:255 +#: mod/contacts.php:621 mod/follow.php:126 mod/notifications.php:255 msgid "Profile URL" msgstr "" -#: mod/contacts.php:636 mod/notifications.php:244 mod/events.php:566 -#: mod/directory.php:145 include/identity.php:308 include/bb2diaspora.php:170 +#: mod/contacts.php:624 mod/notifications.php:244 mod/events.php:566 +#: mod/directory.php:145 include/identity.php:309 include/bb2diaspora.php:170 #: include/event.php:36 include/event.php:60 msgid "Location:" msgstr "" -#: mod/contacts.php:638 mod/notifications.php:246 mod/directory.php:153 -#: include/identity.php:317 include/identity.php:631 +#: mod/contacts.php:626 mod/notifications.php:246 mod/directory.php:153 +#: include/identity.php:318 include/identity.php:632 msgid "About:" msgstr "" -#: mod/contacts.php:640 mod/follow.php:134 mod/notifications.php:248 -#: include/identity.php:625 +#: mod/contacts.php:628 mod/follow.php:134 mod/notifications.php:248 +#: include/identity.php:626 msgid "Tags:" msgstr "" -#: mod/contacts.php:685 +#: mod/contacts.php:629 +msgid "Actions" +msgstr "" + +#: mod/contacts.php:631 mod/contacts.php:825 include/identity.php:687 +#: include/nav.php:75 +msgid "Status" +msgstr "" + +#: mod/contacts.php:632 +msgid "Contact Settings" +msgstr "" + +#: mod/contacts.php:677 msgid "Suggestions" msgstr "" -#: mod/contacts.php:688 +#: mod/contacts.php:680 msgid "Suggest potential friends" msgstr "" -#: mod/contacts.php:693 mod/group.php:192 +#: mod/contacts.php:685 mod/group.php:192 msgid "All Contacts" msgstr "" -#: mod/contacts.php:696 +#: mod/contacts.php:688 msgid "Show all contacts" msgstr "" -#: mod/contacts.php:701 +#: mod/contacts.php:693 msgid "Unblocked" msgstr "" -#: mod/contacts.php:704 +#: mod/contacts.php:696 msgid "Only show unblocked contacts" msgstr "" -#: mod/contacts.php:710 +#: mod/contacts.php:702 msgid "Blocked" msgstr "" -#: mod/contacts.php:713 +#: mod/contacts.php:705 msgid "Only show blocked contacts" msgstr "" -#: mod/contacts.php:719 +#: mod/contacts.php:711 msgid "Ignored" msgstr "" -#: mod/contacts.php:722 +#: mod/contacts.php:714 msgid "Only show ignored contacts" msgstr "" -#: mod/contacts.php:728 +#: mod/contacts.php:720 msgid "Archived" msgstr "" -#: mod/contacts.php:731 +#: mod/contacts.php:723 msgid "Only show archived contacts" msgstr "" -#: mod/contacts.php:737 +#: mod/contacts.php:729 msgid "Hidden" msgstr "" -#: mod/contacts.php:740 +#: mod/contacts.php:732 msgid "Only show hidden contacts" msgstr "" -#: mod/contacts.php:793 mod/contacts.php:841 mod/viewcontacts.php:116 -#: include/identity.php:741 include/identity.php:744 include/text.php:1012 +#: mod/contacts.php:785 mod/contacts.php:845 mod/viewcontacts.php:116 +#: include/identity.php:742 include/identity.php:745 include/text.php:983 #: include/nav.php:123 include/nav.php:187 view/theme/diabook/theme.php:125 msgid "Contacts" msgstr "" -#: mod/contacts.php:797 +#: mod/contacts.php:789 msgid "Search your contacts" msgstr "" -#: mod/contacts.php:798 +#: mod/contacts.php:790 msgid "Finding: " msgstr "" -#: mod/contacts.php:799 mod/directory.php:210 include/contact_widgets.php:34 +#: mod/contacts.php:791 mod/directory.php:210 include/contact_widgets.php:34 msgid "Find" msgstr "" -#: mod/contacts.php:805 mod/settings.php:156 mod/settings.php:685 +#: mod/contacts.php:797 mod/settings.php:156 mod/settings.php:686 msgid "Update" msgstr "" -#: mod/contacts.php:808 mod/contacts.php:879 +#: mod/contacts.php:800 mod/contacts.php:1007 msgid "Archive" msgstr "" -#: mod/contacts.php:808 mod/contacts.php:879 +#: mod/contacts.php:800 mod/contacts.php:1007 msgid "Unarchive" msgstr "" -#: mod/contacts.php:809 mod/group.php:171 mod/admin.php:1310 -#: mod/content.php:440 mod/content.php:743 mod/settings.php:722 -#: mod/photos.php:1723 object/Item.php:134 include/conversation.php:635 +#: mod/contacts.php:801 mod/contacts.php:1015 mod/group.php:171 +#: mod/admin.php:1332 mod/content.php:440 mod/content.php:743 +#: mod/settings.php:723 mod/photos.php:1709 object/Item.php:134 +#: include/conversation.php:635 msgid "Delete" msgstr "" -#: mod/contacts.php:822 include/identity.php:686 include/nav.php:75 -msgid "Status" -msgstr "" - -#: mod/contacts.php:825 mod/follow.php:143 include/identity.php:689 +#: mod/contacts.php:828 mod/follow.php:143 include/identity.php:690 msgid "Status Messages and Posts" msgstr "" -#: mod/contacts.php:830 mod/profperm.php:104 mod/newmember.php:32 -#: include/identity.php:579 include/identity.php:665 include/identity.php:694 +#: mod/contacts.php:833 mod/profperm.php:104 mod/newmember.php:32 +#: include/identity.php:580 include/identity.php:666 include/identity.php:695 #: include/nav.php:76 view/theme/diabook/theme.php:124 msgid "Profile" msgstr "" -#: mod/contacts.php:833 include/identity.php:697 +#: mod/contacts.php:836 include/identity.php:698 msgid "Profile Details" msgstr "" -#: mod/contacts.php:844 +#: mod/contacts.php:848 msgid "View all contacts" msgstr "" -#: mod/contacts.php:850 mod/common.php:134 +#: mod/contacts.php:855 mod/common.php:134 msgid "Common Friends" msgstr "" -#: mod/contacts.php:853 +#: mod/contacts.php:858 msgid "View all common friends" msgstr "" -#: mod/contacts.php:857 -msgid "Repair" +#: mod/contacts.php:862 mod/admin.php:909 +msgid "Advanced" msgstr "" -#: mod/contacts.php:860 +#: mod/contacts.php:865 msgid "Advanced Contact Settings" msgstr "" -#: mod/contacts.php:868 -msgid "Toggle Blocked status" -msgstr "" - -#: mod/contacts.php:875 -msgid "Toggle Ignored status" -msgstr "" - -#: mod/contacts.php:882 -msgid "Toggle Archive status" -msgstr "" - -#: mod/contacts.php:924 +#: mod/contacts.php:910 msgid "Mutual Friendship" msgstr "" -#: mod/contacts.php:928 +#: mod/contacts.php:914 msgid "is a fan of yours" msgstr "" -#: mod/contacts.php:932 +#: mod/contacts.php:918 msgid "you are a fan of" msgstr "" -#: mod/contacts.php:953 mod/nogroup.php:42 +#: mod/contacts.php:939 mod/nogroup.php:42 msgid "Edit contact" msgstr "" +#: mod/contacts.php:993 +msgid "Toggle Blocked status" +msgstr "" + +#: mod/contacts.php:1001 +msgid "Toggle Ignored status" +msgstr "" + +#: mod/contacts.php:1009 +msgid "Toggle Archive status" +msgstr "" + +#: mod/contacts.php:1017 +msgid "Delete contact" +msgstr "" + #: mod/hcard.php:10 msgid "No profile" msgstr "" @@ -522,7 +532,7 @@ msgstr "" msgid "Post successful." msgstr "" -#: mod/profperm.php:19 mod/group.php:72 index.php:382 +#: mod/profperm.php:19 mod/group.php:72 index.php:383 msgid "Permission denied" msgstr "" @@ -546,23 +556,23 @@ msgstr "" msgid "All Contacts (with secure profile access)" msgstr "" -#: mod/display.php:82 mod/display.php:291 mod/display.php:513 -#: mod/viewsrc.php:15 mod/admin.php:234 mod/admin.php:1365 mod/admin.php:1599 -#: mod/notice.php:15 include/items.php:4887 +#: mod/display.php:82 mod/display.php:298 mod/display.php:517 +#: mod/viewsrc.php:15 mod/admin.php:234 mod/admin.php:1387 mod/admin.php:1621 +#: mod/notice.php:15 include/items.php:1793 msgid "Item not found." msgstr "" -#: mod/display.php:220 mod/videos.php:197 mod/viewcontacts.php:35 -#: mod/community.php:22 mod/dfrn_request.php:786 mod/search.php:93 -#: mod/search.php:99 mod/directory.php:37 mod/photos.php:976 +#: mod/display.php:227 mod/videos.php:197 mod/viewcontacts.php:35 +#: mod/community.php:22 mod/dfrn_request.php:784 mod/search.php:93 +#: mod/search.php:99 mod/directory.php:37 mod/photos.php:962 msgid "Public access denied." msgstr "" -#: mod/display.php:339 mod/profile.php:155 +#: mod/display.php:346 mod/profile.php:155 msgid "Access to this profile has been restricted." msgstr "" -#: mod/display.php:506 +#: mod/display.php:510 msgid "Item has been removed." msgstr "" @@ -597,7 +607,7 @@ msgid "" "join." msgstr "" -#: mod/newmember.php:22 mod/admin.php:1418 mod/admin.php:1676 +#: mod/newmember.php:22 mod/admin.php:1440 mod/admin.php:1698 #: mod/settings.php:109 include/nav.php:182 view/theme/diabook/theme.php:544 #: view/theme/diabook/theme.php:648 msgid "Settings" @@ -622,7 +632,7 @@ msgid "" "potential friends know exactly how to find you." msgstr "" -#: mod/newmember.php:36 mod/profile_photo.php:250 mod/profiles.php:709 +#: mod/newmember.php:36 mod/profile_photo.php:250 mod/profiles.php:687 msgid "Upload Profile Photo" msgstr "" @@ -705,7 +715,7 @@ msgid "" "hours." msgstr "" -#: mod/newmember.php:61 include/group.php:283 +#: mod/newmember.php:61 include/group.php:286 msgid "Groups" msgstr "" @@ -765,8 +775,8 @@ msgstr "" #: mod/profile_photo.php:74 mod/profile_photo.php:81 mod/profile_photo.php:88 #: mod/profile_photo.php:210 mod/profile_photo.php:302 #: mod/profile_photo.php:311 mod/photos.php:78 mod/photos.php:192 -#: mod/photos.php:775 mod/photos.php:1245 mod/photos.php:1268 -#: mod/photos.php:1862 include/user.php:345 include/user.php:352 +#: mod/photos.php:769 mod/photos.php:1231 mod/photos.php:1254 +#: mod/photos.php:1848 include/user.php:345 include/user.php:352 #: include/user.php:359 view/theme/diabook/theme.php:500 msgid "Profile Photos" msgstr "" @@ -787,12 +797,12 @@ msgstr "" msgid "Unable to process image" msgstr "" -#: mod/profile_photo.php:150 mod/wall_upload.php:151 mod/photos.php:811 +#: mod/profile_photo.php:150 mod/wall_upload.php:151 mod/photos.php:805 #, php-format msgid "Image exceeds size limit of %s" msgstr "" -#: mod/profile_photo.php:159 mod/wall_upload.php:183 mod/photos.php:851 +#: mod/profile_photo.php:159 mod/wall_upload.php:188 mod/photos.php:845 msgid "Unable to process image." msgstr "" @@ -836,13 +846,13 @@ msgstr "" msgid "Image uploaded successfully." msgstr "" -#: mod/profile_photo.php:307 mod/wall_upload.php:216 mod/photos.php:878 +#: mod/profile_photo.php:307 mod/wall_upload.php:221 mod/photos.php:872 msgid "Image upload failed." msgstr "" #: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165 #: include/conversation.php:130 include/conversation.php:266 -#: include/text.php:2000 include/diaspora.php:2169 +#: include/text.php:1923 include/diaspora.php:2117 #: view/theme/diabook/theme.php:471 msgid "photo" msgstr "" @@ -850,7 +860,7 @@ msgstr "" #: mod/subthread.php:87 mod/tagger.php:62 include/like.php:165 #: include/like.php:334 include/conversation.php:125 #: include/conversation.php:134 include/conversation.php:261 -#: include/conversation.php:270 include/diaspora.php:2169 +#: include/conversation.php:270 include/diaspora.php:2117 #: view/theme/diabook/theme.php:466 view/theme/diabook/theme.php:475 msgid "status" msgstr "" @@ -920,11 +930,11 @@ msgstr "" msgid "- select -" msgstr "" -#: mod/filer.php:31 mod/editpost.php:109 mod/notes.php:61 include/text.php:1004 +#: mod/filer.php:31 mod/editpost.php:109 mod/notes.php:61 include/text.php:975 msgid "Save" msgstr "" -#: mod/follow.php:19 mod/dfrn_request.php:870 +#: mod/follow.php:19 mod/dfrn_request.php:868 msgid "Submit Request" msgstr "" @@ -944,30 +954,30 @@ msgstr "" msgid "The network type couldn't be detected. Contact can't be added." msgstr "" -#: mod/follow.php:109 mod/dfrn_request.php:856 +#: mod/follow.php:109 mod/dfrn_request.php:854 msgid "Please answer the following:" msgstr "" -#: mod/follow.php:110 mod/dfrn_request.php:857 +#: mod/follow.php:110 mod/dfrn_request.php:855 #, php-format msgid "Does %s know you?" msgstr "" -#: mod/follow.php:110 mod/settings.php:1103 mod/settings.php:1109 -#: mod/settings.php:1117 mod/settings.php:1121 mod/settings.php:1126 -#: mod/settings.php:1132 mod/settings.php:1138 mod/settings.php:1144 -#: mod/settings.php:1170 mod/settings.php:1171 mod/settings.php:1172 -#: mod/settings.php:1173 mod/settings.php:1174 mod/dfrn_request.php:857 -#: mod/register.php:239 mod/profiles.php:658 mod/profiles.php:662 -#: mod/profiles.php:687 mod/api.php:106 +#: mod/follow.php:110 mod/settings.php:1107 mod/settings.php:1113 +#: mod/settings.php:1121 mod/settings.php:1125 mod/settings.php:1130 +#: mod/settings.php:1136 mod/settings.php:1142 mod/settings.php:1148 +#: mod/settings.php:1174 mod/settings.php:1175 mod/settings.php:1176 +#: mod/settings.php:1177 mod/settings.php:1178 mod/dfrn_request.php:855 +#: mod/register.php:239 mod/profiles.php:636 mod/profiles.php:640 +#: mod/profiles.php:665 mod/api.php:106 msgid "No" msgstr "" -#: mod/follow.php:111 mod/dfrn_request.php:861 +#: mod/follow.php:111 mod/dfrn_request.php:859 msgid "Add a personal note:" msgstr "" -#: mod/follow.php:117 mod/dfrn_request.php:867 +#: mod/follow.php:117 mod/dfrn_request.php:865 msgid "Your Identity Address:" msgstr "" @@ -979,17 +989,17 @@ msgstr "" msgid "Unable to locate original post." msgstr "" -#: mod/item.php:329 +#: mod/item.php:332 msgid "Empty post discarded." msgstr "" -#: mod/item.php:467 mod/wall_upload.php:213 mod/wall_upload.php:227 -#: mod/wall_upload.php:234 include/Photo.php:958 include/Photo.php:973 -#: include/Photo.php:980 include/Photo.php:1002 include/message.php:145 +#: mod/item.php:470 mod/wall_upload.php:218 mod/wall_upload.php:232 +#: mod/wall_upload.php:239 include/Photo.php:994 include/Photo.php:1009 +#: include/Photo.php:1016 include/Photo.php:1038 include/message.php:145 msgid "Wall Photos" msgstr "" -#: mod/item.php:842 +#: mod/item.php:845 msgid "System error. Post not saved." msgstr "" @@ -1039,7 +1049,7 @@ msgstr "" msgid "Create a group of contacts/friends." msgstr "" -#: mod/group.php:94 mod/group.php:178 include/group.php:289 +#: mod/group.php:94 mod/group.php:178 include/group.php:292 msgid "Group Name: " msgstr "" @@ -1063,7 +1073,7 @@ msgstr "" msgid "Group is empty" msgstr "" -#: mod/apps.php:7 index.php:226 +#: mod/apps.php:7 index.php:227 msgid "You must be logged in to use addons. " msgstr "" @@ -1076,12 +1086,12 @@ msgid "No installed applications." msgstr "" #: mod/dfrn_confirm.php:64 mod/profiles.php:18 mod/profiles.php:133 -#: mod/profiles.php:179 mod/profiles.php:627 +#: mod/profiles.php:179 mod/profiles.php:605 msgid "Profile not found." msgstr "" #: mod/dfrn_confirm.php:120 mod/fsuggest.php:20 mod/fsuggest.php:92 -#: mod/crepair.php:131 +#: mod/crepair.php:114 msgid "Contact not found." msgstr "" @@ -1115,57 +1125,57 @@ msgstr "" msgid "Introduction failed or was revoked." msgstr "" -#: mod/dfrn_confirm.php:430 +#: mod/dfrn_confirm.php:413 msgid "Unable to set contact photo." msgstr "" -#: mod/dfrn_confirm.php:487 include/conversation.php:185 -#: include/diaspora.php:637 +#: mod/dfrn_confirm.php:470 include/conversation.php:185 +#: include/diaspora.php:638 #, php-format msgid "%1$s is now friends with %2$s" msgstr "" -#: mod/dfrn_confirm.php:572 +#: mod/dfrn_confirm.php:552 #, php-format msgid "No user record found for '%s' " msgstr "" -#: mod/dfrn_confirm.php:582 +#: mod/dfrn_confirm.php:562 msgid "Our site encryption key is apparently messed up." msgstr "" -#: mod/dfrn_confirm.php:593 +#: mod/dfrn_confirm.php:573 msgid "Empty site URL was provided or URL could not be decrypted by us." msgstr "" -#: mod/dfrn_confirm.php:614 +#: mod/dfrn_confirm.php:594 msgid "Contact record was not found for you on our site." msgstr "" -#: mod/dfrn_confirm.php:628 +#: mod/dfrn_confirm.php:608 #, php-format msgid "Site public key not available in contact record for URL %s." msgstr "" -#: mod/dfrn_confirm.php:648 +#: mod/dfrn_confirm.php:628 msgid "" "The ID provided by your system is a duplicate on our system. It should work " "if you try again." msgstr "" -#: mod/dfrn_confirm.php:659 +#: mod/dfrn_confirm.php:639 msgid "Unable to set your contact credentials on our system." msgstr "" -#: mod/dfrn_confirm.php:726 +#: mod/dfrn_confirm.php:698 msgid "Unable to update your contact profile details on our system" msgstr "" -#: mod/dfrn_confirm.php:753 mod/dfrn_request.php:741 include/items.php:4299 +#: mod/dfrn_confirm.php:725 mod/dfrn_request.php:739 include/items.php:1434 msgid "[Name Withheld]" msgstr "" -#: mod/dfrn_confirm.php:798 +#: mod/dfrn_confirm.php:770 #, php-format msgid "%1$s has joined %2$s" msgstr "" @@ -1190,15 +1200,15 @@ msgstr "" msgid "No videos selected" msgstr "" -#: mod/videos.php:308 mod/photos.php:1087 +#: mod/videos.php:308 mod/photos.php:1073 msgid "Access to this item is restricted." msgstr "" -#: mod/videos.php:383 include/text.php:1472 +#: mod/videos.php:383 include/text.php:1443 msgid "View Video" msgstr "" -#: mod/videos.php:390 mod/photos.php:1890 +#: mod/videos.php:390 mod/photos.php:1876 msgid "View Album" msgstr "" @@ -1230,7 +1240,7 @@ msgstr "" #: mod/wall_upload.php:20 mod/wall_upload.php:33 mod/wall_upload.php:86 #: mod/wall_upload.php:122 mod/wall_upload.php:125 mod/wall_attach.php:17 -#: mod/wall_attach.php:25 mod/wall_attach.php:76 include/api.php:1781 +#: mod/wall_attach.php:25 mod/wall_attach.php:76 msgid "Invalid request." msgstr "" @@ -1288,7 +1298,7 @@ msgid "" "Password reset failed." msgstr "" -#: mod/lostpass.php:109 boot.php:1444 +#: mod/lostpass.php:109 boot.php:1534 msgid "Password Reset" msgstr "" @@ -1364,15 +1374,15 @@ msgstr "" msgid "Reset" msgstr "" -#: mod/ping.php:265 +#: mod/ping.php:267 msgid "{0} wants to be your friend" msgstr "" -#: mod/ping.php:280 +#: mod/ping.php:282 msgid "{0} sent you a message" msgstr "" -#: mod/ping.php:295 +#: mod/ping.php:297 msgid "{0} requested registration" msgstr "" @@ -1392,7 +1402,7 @@ msgstr "" msgid "System" msgstr "" -#: mod/notifications.php:87 mod/admin.php:390 include/nav.php:154 +#: mod/notifications.php:87 mod/admin.php:399 include/nav.php:154 msgid "Network" msgstr "" @@ -1438,7 +1448,7 @@ msgstr "" msgid "if applicable" msgstr "" -#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1308 +#: mod/notifications.php:176 mod/notifications.php:257 mod/admin.php:1330 msgid "Approve" msgstr "" @@ -1488,8 +1498,8 @@ msgstr "" msgid "New Follower" msgstr "" -#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:310 -#: include/identity.php:590 +#: mod/notifications.php:250 mod/directory.php:147 include/identity.php:311 +#: include/identity.php:591 msgid "Gender:" msgstr "" @@ -1538,11 +1548,11 @@ msgstr "" msgid "Network Notifications" msgstr "" -#: mod/notifications.php:385 mod/notify.php:72 +#: mod/notifications.php:385 mod/notify.php:60 msgid "No more system notifications." msgstr "" -#: mod/notifications.php:389 mod/notify.php:76 +#: mod/notifications.php:389 mod/notify.php:64 msgid "System Notifications" msgstr "" @@ -1693,7 +1703,7 @@ msgstr "" #: mod/message.php:341 mod/message.php:526 mod/content.php:501 #: mod/content.php:885 mod/wallmessage.php:156 mod/editpost.php:124 -#: mod/photos.php:1610 object/Item.php:396 include/conversation.php:713 +#: mod/photos.php:1596 object/Item.php:396 include/conversation.php:713 #: include/conversation.php:1201 msgid "Please wait" msgstr "" @@ -1755,98 +1765,98 @@ msgstr[1] "" msgid "[Embedded content - reload page to view]" msgstr "" -#: mod/crepair.php:104 +#: mod/crepair.php:87 msgid "Contact settings applied." msgstr "" -#: mod/crepair.php:106 +#: mod/crepair.php:89 msgid "Contact update failed." msgstr "" -#: mod/crepair.php:137 +#: mod/crepair.php:120 msgid "" "<strong>WARNING: This is highly advanced</strong> and if you enter incorrect " "information your communications with this contact may stop working." msgstr "" -#: mod/crepair.php:138 +#: mod/crepair.php:121 msgid "" "Please use your browser 'Back' button <strong>now</strong> if you are " "uncertain what to do on this page." msgstr "" -#: mod/crepair.php:151 mod/crepair.php:153 +#: mod/crepair.php:134 mod/crepair.php:136 msgid "No mirroring" msgstr "" -#: mod/crepair.php:151 +#: mod/crepair.php:134 msgid "Mirror as forwarded posting" msgstr "" -#: mod/crepair.php:151 mod/crepair.php:153 +#: mod/crepair.php:134 mod/crepair.php:136 msgid "Mirror as my own posting" msgstr "" -#: mod/crepair.php:167 +#: mod/crepair.php:150 msgid "Return to contact editor" msgstr "" -#: mod/crepair.php:169 +#: mod/crepair.php:152 msgid "Refetch contact data" msgstr "" -#: mod/crepair.php:170 mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319 -#: mod/admin.php:1332 mod/settings.php:661 mod/settings.php:687 +#: mod/crepair.php:153 mod/admin.php:1328 mod/admin.php:1340 mod/admin.php:1341 +#: mod/admin.php:1354 mod/settings.php:662 mod/settings.php:688 msgid "Name" msgstr "" -#: mod/crepair.php:171 +#: mod/crepair.php:154 msgid "Account Nickname" msgstr "" -#: mod/crepair.php:172 +#: mod/crepair.php:155 msgid "@Tagname - overrides Name/Nickname" msgstr "" -#: mod/crepair.php:173 +#: mod/crepair.php:156 msgid "Account URL" msgstr "" -#: mod/crepair.php:174 +#: mod/crepair.php:157 msgid "Friend Request URL" msgstr "" -#: mod/crepair.php:175 +#: mod/crepair.php:158 msgid "Friend Confirm URL" msgstr "" -#: mod/crepair.php:176 +#: mod/crepair.php:159 msgid "Notification Endpoint URL" msgstr "" -#: mod/crepair.php:177 +#: mod/crepair.php:160 msgid "Poll/Feed URL" msgstr "" -#: mod/crepair.php:178 +#: mod/crepair.php:161 msgid "New photo from this URL" msgstr "" -#: mod/crepair.php:179 +#: mod/crepair.php:162 msgid "Remote Self" msgstr "" -#: mod/crepair.php:182 +#: mod/crepair.php:165 msgid "Mirror postings from this contact" msgstr "" -#: mod/crepair.php:184 +#: mod/crepair.php:167 msgid "" "Mark this contact as remote_self, this will cause friendica to repost new " "entries from this contact." msgstr "" -#: mod/bookmarklet.php:12 boot.php:1430 include/nav.php:91 +#: mod/bookmarklet.php:12 boot.php:1520 include/nav.php:91 msgid "Login" msgstr "" @@ -1864,8 +1874,8 @@ msgid "Connect" msgstr "" #: mod/dirfind.php:195 mod/allfriends.php:64 mod/match.php:70 -#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:283 -#: include/Contact.php:296 include/Contact.php:338 include/conversation.php:912 +#: mod/directory.php:162 mod/suggest.php:81 include/Contact.php:285 +#: include/Contact.php:298 include/Contact.php:340 include/conversation.php:912 #: include/conversation.php:926 msgid "View Profile" msgstr "" @@ -1879,14 +1889,14 @@ msgstr "" msgid "No matches" msgstr "" -#: mod/fbrowser.php:32 include/identity.php:702 include/nav.php:77 +#: mod/fbrowser.php:32 include/identity.php:703 include/nav.php:77 #: view/theme/diabook/theme.php:126 msgid "Photos" msgstr "" #: mod/fbrowser.php:41 mod/fbrowser.php:62 mod/photos.php:62 mod/photos.php:192 -#: mod/photos.php:1119 mod/photos.php:1245 mod/photos.php:1268 -#: mod/photos.php:1838 mod/photos.php:1850 view/theme/diabook/theme.php:499 +#: mod/photos.php:1105 mod/photos.php:1231 mod/photos.php:1254 +#: mod/photos.php:1824 mod/photos.php:1836 view/theme/diabook/theme.php:499 msgid "Contact Photos" msgstr "" @@ -1902,19 +1912,19 @@ msgstr "" msgid "Theme settings updated." msgstr "" -#: mod/admin.php:156 mod/admin.php:888 +#: mod/admin.php:156 mod/admin.php:904 msgid "Site" msgstr "" -#: mod/admin.php:157 mod/admin.php:832 mod/admin.php:1301 mod/admin.php:1316 +#: mod/admin.php:157 mod/admin.php:848 mod/admin.php:1323 mod/admin.php:1338 msgid "Users" msgstr "" -#: mod/admin.php:158 mod/admin.php:1416 mod/admin.php:1476 mod/settings.php:72 +#: mod/admin.php:158 mod/admin.php:1438 mod/admin.php:1498 mod/settings.php:72 msgid "Plugins" msgstr "" -#: mod/admin.php:159 mod/admin.php:1674 mod/admin.php:1724 +#: mod/admin.php:159 mod/admin.php:1696 mod/admin.php:1746 msgid "Themes" msgstr "" @@ -1926,19 +1936,19 @@ msgstr "" msgid "DB updates" msgstr "" -#: mod/admin.php:162 mod/admin.php:385 +#: mod/admin.php:162 mod/admin.php:394 msgid "Inspect Queue" msgstr "" -#: mod/admin.php:163 mod/admin.php:354 +#: mod/admin.php:163 mod/admin.php:363 msgid "Federation Statistics" msgstr "" -#: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1792 +#: mod/admin.php:177 mod/admin.php:188 mod/admin.php:1814 msgid "Logs" msgstr "" -#: mod/admin.php:178 mod/admin.php:1859 +#: mod/admin.php:178 mod/admin.php:1881 msgid "View Logs" msgstr "" @@ -1966,739 +1976,750 @@ msgstr "" msgid "User registrations waiting for confirmation" msgstr "" -#: mod/admin.php:347 +#: mod/admin.php:356 msgid "" "This page offers you some numbers to the known part of the federated social " "network your Friendica node is part of. These numbers are not complete but " "only reflect the part of the network your node is aware of." msgstr "" -#: mod/admin.php:348 +#: mod/admin.php:357 msgid "" "The <em>Auto Discovered Contact Directory</em> feature is not enabled, it " "will improve the data displayed here." msgstr "" -#: mod/admin.php:353 mod/admin.php:384 mod/admin.php:441 mod/admin.php:887 -#: mod/admin.php:1300 mod/admin.php:1415 mod/admin.php:1475 mod/admin.php:1673 -#: mod/admin.php:1723 mod/admin.php:1791 mod/admin.php:1858 +#: mod/admin.php:362 mod/admin.php:393 mod/admin.php:450 mod/admin.php:903 +#: mod/admin.php:1322 mod/admin.php:1437 mod/admin.php:1497 mod/admin.php:1695 +#: mod/admin.php:1745 mod/admin.php:1813 mod/admin.php:1880 msgid "Administration" msgstr "" -#: mod/admin.php:360 +#: mod/admin.php:369 #, php-format msgid "Currently this node is aware of %d nodes from the following platforms:" msgstr "" -#: mod/admin.php:387 +#: mod/admin.php:396 msgid "ID" msgstr "" -#: mod/admin.php:388 +#: mod/admin.php:397 msgid "Recipient Name" msgstr "" -#: mod/admin.php:389 +#: mod/admin.php:398 msgid "Recipient Profile" msgstr "" -#: mod/admin.php:391 +#: mod/admin.php:400 msgid "Created" msgstr "" -#: mod/admin.php:392 +#: mod/admin.php:401 msgid "Last Tried" msgstr "" -#: mod/admin.php:393 +#: mod/admin.php:402 msgid "" "This page lists the content of the queue for outgoing postings. These are " "postings the initial delivery failed for. They will be resend later and " "eventually deleted if the delivery fails permanently." msgstr "" -#: mod/admin.php:412 mod/admin.php:1254 +#: mod/admin.php:421 mod/admin.php:1276 msgid "Normal Account" msgstr "" -#: mod/admin.php:413 mod/admin.php:1255 +#: mod/admin.php:422 mod/admin.php:1277 msgid "Soapbox Account" msgstr "" -#: mod/admin.php:414 mod/admin.php:1256 +#: mod/admin.php:423 mod/admin.php:1278 msgid "Community/Celebrity Account" msgstr "" -#: mod/admin.php:415 mod/admin.php:1257 +#: mod/admin.php:424 mod/admin.php:1279 msgid "Automatic Friend Account" msgstr "" -#: mod/admin.php:416 +#: mod/admin.php:425 msgid "Blog Account" msgstr "" -#: mod/admin.php:417 +#: mod/admin.php:426 msgid "Private Forum" msgstr "" -#: mod/admin.php:436 +#: mod/admin.php:445 msgid "Message queues" msgstr "" -#: mod/admin.php:442 +#: mod/admin.php:451 msgid "Summary" msgstr "" -#: mod/admin.php:444 +#: mod/admin.php:453 msgid "Registered users" msgstr "" -#: mod/admin.php:446 +#: mod/admin.php:455 msgid "Pending registrations" msgstr "" -#: mod/admin.php:447 +#: mod/admin.php:456 msgid "Version" msgstr "" -#: mod/admin.php:452 +#: mod/admin.php:461 msgid "Active plugins" msgstr "" -#: mod/admin.php:475 +#: mod/admin.php:484 msgid "Can not parse base url. Must have at least <scheme>://<domain>" msgstr "" -#: mod/admin.php:760 +#: mod/admin.php:776 msgid "RINO2 needs mcrypt php extension to work." msgstr "" -#: mod/admin.php:768 +#: mod/admin.php:784 msgid "Site settings updated." msgstr "" -#: mod/admin.php:796 mod/settings.php:912 +#: mod/admin.php:812 mod/settings.php:916 msgid "No special theme for mobile devices" msgstr "" -#: mod/admin.php:815 +#: mod/admin.php:831 msgid "No community page" msgstr "" -#: mod/admin.php:816 +#: mod/admin.php:832 msgid "Public postings from users of this site" msgstr "" -#: mod/admin.php:817 +#: mod/admin.php:833 msgid "Global community page" msgstr "" -#: mod/admin.php:823 +#: mod/admin.php:839 msgid "At post arrival" msgstr "" -#: mod/admin.php:824 include/contact_selectors.php:56 +#: mod/admin.php:840 include/contact_selectors.php:56 msgid "Frequently" msgstr "" -#: mod/admin.php:825 include/contact_selectors.php:57 +#: mod/admin.php:841 include/contact_selectors.php:57 msgid "Hourly" msgstr "" -#: mod/admin.php:826 include/contact_selectors.php:58 +#: mod/admin.php:842 include/contact_selectors.php:58 msgid "Twice daily" msgstr "" -#: mod/admin.php:827 include/contact_selectors.php:59 +#: mod/admin.php:843 include/contact_selectors.php:59 msgid "Daily" msgstr "" -#: mod/admin.php:833 +#: mod/admin.php:849 msgid "Users, Global Contacts" msgstr "" -#: mod/admin.php:834 +#: mod/admin.php:850 msgid "Users, Global Contacts/fallback" msgstr "" -#: mod/admin.php:838 +#: mod/admin.php:854 msgid "One month" msgstr "" -#: mod/admin.php:839 +#: mod/admin.php:855 msgid "Three months" msgstr "" -#: mod/admin.php:840 +#: mod/admin.php:856 msgid "Half a year" msgstr "" -#: mod/admin.php:841 +#: mod/admin.php:857 msgid "One year" msgstr "" -#: mod/admin.php:846 +#: mod/admin.php:862 msgid "Multi user instance" msgstr "" -#: mod/admin.php:869 +#: mod/admin.php:885 msgid "Closed" msgstr "" -#: mod/admin.php:870 +#: mod/admin.php:886 msgid "Requires approval" msgstr "" -#: mod/admin.php:871 +#: mod/admin.php:887 msgid "Open" msgstr "" -#: mod/admin.php:875 +#: mod/admin.php:891 msgid "No SSL policy, links will track page SSL state" msgstr "" -#: mod/admin.php:876 +#: mod/admin.php:892 msgid "Force all links to use SSL" msgstr "" -#: mod/admin.php:877 +#: mod/admin.php:893 msgid "Self-signed certificate, use SSL for local links only (discouraged)" msgstr "" -#: mod/admin.php:889 mod/admin.php:1477 mod/admin.php:1725 mod/admin.php:1793 -#: mod/admin.php:1942 mod/settings.php:659 mod/settings.php:769 -#: mod/settings.php:813 mod/settings.php:882 mod/settings.php:969 -#: mod/settings.php:1204 +#: mod/admin.php:905 mod/admin.php:1499 mod/admin.php:1747 mod/admin.php:1815 +#: mod/admin.php:1964 mod/settings.php:660 mod/settings.php:770 +#: mod/settings.php:817 mod/settings.php:886 mod/settings.php:973 +#: mod/settings.php:1208 msgid "Save Settings" msgstr "" -#: mod/admin.php:890 mod/register.php:263 +#: mod/admin.php:906 mod/register.php:263 msgid "Registration" msgstr "" -#: mod/admin.php:891 +#: mod/admin.php:907 msgid "File upload" msgstr "" -#: mod/admin.php:892 +#: mod/admin.php:908 msgid "Policies" msgstr "" -#: mod/admin.php:893 -msgid "Advanced" -msgstr "" - -#: mod/admin.php:894 +#: mod/admin.php:910 msgid "Auto Discovered Contact Directory" msgstr "" -#: mod/admin.php:895 +#: mod/admin.php:911 msgid "Performance" msgstr "" -#: mod/admin.php:896 +#: mod/admin.php:912 +msgid "Worker" +msgstr "" + +#: mod/admin.php:913 msgid "" "Relocate - WARNING: advanced function. Could make this server unreachable." msgstr "" -#: mod/admin.php:899 +#: mod/admin.php:916 msgid "Site name" msgstr "" -#: mod/admin.php:900 +#: mod/admin.php:917 msgid "Host name" msgstr "" -#: mod/admin.php:901 +#: mod/admin.php:918 msgid "Sender Email" msgstr "" -#: mod/admin.php:901 +#: mod/admin.php:918 msgid "" "The email address your server shall use to send notification emails from." msgstr "" -#: mod/admin.php:902 +#: mod/admin.php:919 msgid "Banner/Logo" msgstr "" -#: mod/admin.php:903 +#: mod/admin.php:920 msgid "Shortcut icon" msgstr "" -#: mod/admin.php:903 +#: mod/admin.php:920 msgid "Link to an icon that will be used for browsers." msgstr "" -#: mod/admin.php:904 +#: mod/admin.php:921 msgid "Touch icon" msgstr "" -#: mod/admin.php:904 +#: mod/admin.php:921 msgid "Link to an icon that will be used for tablets and mobiles." msgstr "" -#: mod/admin.php:905 +#: mod/admin.php:922 msgid "Additional Info" msgstr "" -#: mod/admin.php:905 +#: mod/admin.php:922 #, php-format msgid "" "For public servers: you can add additional information here that will be " "listed at %s/siteinfo." msgstr "" -#: mod/admin.php:906 +#: mod/admin.php:923 msgid "System language" msgstr "" -#: mod/admin.php:907 +#: mod/admin.php:924 msgid "System theme" msgstr "" -#: mod/admin.php:907 +#: mod/admin.php:924 msgid "" "Default system theme - may be over-ridden by user profiles - <a href='#' " "id='cnftheme'>change theme settings</a>" msgstr "" -#: mod/admin.php:908 +#: mod/admin.php:925 msgid "Mobile system theme" msgstr "" -#: mod/admin.php:908 +#: mod/admin.php:925 msgid "Theme for mobile devices" msgstr "" -#: mod/admin.php:909 +#: mod/admin.php:926 msgid "SSL link policy" msgstr "" -#: mod/admin.php:909 +#: mod/admin.php:926 msgid "Determines whether generated links should be forced to use SSL" msgstr "" -#: mod/admin.php:910 +#: mod/admin.php:927 msgid "Force SSL" msgstr "" -#: mod/admin.php:910 +#: mod/admin.php:927 msgid "" "Force all Non-SSL requests to SSL - Attention: on some systems it could lead " "to endless loops." msgstr "" -#: mod/admin.php:911 +#: mod/admin.php:928 msgid "Old style 'Share'" msgstr "" -#: mod/admin.php:911 +#: mod/admin.php:928 msgid "Deactivates the bbcode element 'share' for repeating items." msgstr "" -#: mod/admin.php:912 +#: mod/admin.php:929 msgid "Hide help entry from navigation menu" msgstr "" -#: mod/admin.php:912 +#: mod/admin.php:929 msgid "" "Hides the menu entry for the Help pages from the navigation menu. You can " "still access it calling /help directly." msgstr "" -#: mod/admin.php:913 +#: mod/admin.php:930 msgid "Single user instance" msgstr "" -#: mod/admin.php:913 +#: mod/admin.php:930 msgid "Make this instance multi-user or single-user for the named user" msgstr "" -#: mod/admin.php:914 +#: mod/admin.php:931 msgid "Maximum image size" msgstr "" -#: mod/admin.php:914 +#: mod/admin.php:931 msgid "" "Maximum size in bytes of uploaded images. Default is 0, which means no " "limits." msgstr "" -#: mod/admin.php:915 +#: mod/admin.php:932 msgid "Maximum image length" msgstr "" -#: mod/admin.php:915 +#: mod/admin.php:932 msgid "" "Maximum length in pixels of the longest side of uploaded images. Default is " "-1, which means no limits." msgstr "" -#: mod/admin.php:916 +#: mod/admin.php:933 msgid "JPEG image quality" msgstr "" -#: mod/admin.php:916 +#: mod/admin.php:933 msgid "" "Uploaded JPEGS will be saved at this quality setting [0-100]. Default is " "100, which is full quality." msgstr "" -#: mod/admin.php:918 +#: mod/admin.php:935 msgid "Register policy" msgstr "" -#: mod/admin.php:919 +#: mod/admin.php:936 msgid "Maximum Daily Registrations" msgstr "" -#: mod/admin.php:919 +#: mod/admin.php:936 msgid "" "If registration is permitted above, this sets the maximum number of new user " "registrations to accept per day. If register is set to closed, this setting " "has no effect." msgstr "" -#: mod/admin.php:920 +#: mod/admin.php:937 msgid "Register text" msgstr "" -#: mod/admin.php:920 +#: mod/admin.php:937 msgid "Will be displayed prominently on the registration page." msgstr "" -#: mod/admin.php:921 +#: mod/admin.php:938 msgid "Accounts abandoned after x days" msgstr "" -#: mod/admin.php:921 +#: mod/admin.php:938 msgid "" "Will not waste system resources polling external sites for abandonded " "accounts. Enter 0 for no time limit." msgstr "" -#: mod/admin.php:922 +#: mod/admin.php:939 msgid "Allowed friend domains" msgstr "" -#: mod/admin.php:922 +#: mod/admin.php:939 msgid "" "Comma separated list of domains which are allowed to establish friendships " "with this site. Wildcards are accepted. Empty to allow any domains" msgstr "" -#: mod/admin.php:923 +#: mod/admin.php:940 msgid "Allowed email domains" msgstr "" -#: mod/admin.php:923 +#: mod/admin.php:940 msgid "" "Comma separated list of domains which are allowed in email addresses for " "registrations to this site. Wildcards are accepted. Empty to allow any " "domains" msgstr "" -#: mod/admin.php:924 +#: mod/admin.php:941 msgid "Block public" msgstr "" -#: mod/admin.php:924 +#: mod/admin.php:941 msgid "" "Check to block public access to all otherwise public personal pages on this " "site unless you are currently logged in." msgstr "" -#: mod/admin.php:925 +#: mod/admin.php:942 msgid "Force publish" msgstr "" -#: mod/admin.php:925 +#: mod/admin.php:942 msgid "" "Check to force all profiles on this site to be listed in the site directory." msgstr "" -#: mod/admin.php:926 +#: mod/admin.php:943 msgid "Global directory URL" msgstr "" -#: mod/admin.php:926 +#: mod/admin.php:943 msgid "" "URL to the global directory. If this is not set, the global directory is " "completely unavailable to the application." msgstr "" -#: mod/admin.php:927 +#: mod/admin.php:944 msgid "Allow threaded items" msgstr "" -#: mod/admin.php:927 +#: mod/admin.php:944 msgid "Allow infinite level threading for items on this site." msgstr "" -#: mod/admin.php:928 +#: mod/admin.php:945 msgid "Private posts by default for new users" msgstr "" -#: mod/admin.php:928 +#: mod/admin.php:945 msgid "" "Set default post permissions for all new members to the default privacy " "group rather than public." msgstr "" -#: mod/admin.php:929 +#: mod/admin.php:946 msgid "Don't include post content in email notifications" msgstr "" -#: mod/admin.php:929 +#: mod/admin.php:946 msgid "" "Don't include the content of a post/comment/private message/etc. in the " "email notifications that are sent out from this site, as a privacy measure." msgstr "" -#: mod/admin.php:930 +#: mod/admin.php:947 msgid "Disallow public access to addons listed in the apps menu." msgstr "" -#: mod/admin.php:930 +#: mod/admin.php:947 msgid "" "Checking this box will restrict addons listed in the apps menu to members " "only." msgstr "" -#: mod/admin.php:931 +#: mod/admin.php:948 msgid "Don't embed private images in posts" msgstr "" -#: mod/admin.php:931 +#: mod/admin.php:948 msgid "" "Don't replace locally-hosted private photos in posts with an embedded copy " "of the image. This means that contacts who receive posts containing private " "photos will have to authenticate and load each image, which may take a while." msgstr "" -#: mod/admin.php:932 +#: mod/admin.php:949 msgid "Allow Users to set remote_self" msgstr "" -#: mod/admin.php:932 +#: mod/admin.php:949 msgid "" "With checking this, every user is allowed to mark every contact as a " "remote_self in the repair contact dialog. Setting this flag on a contact " "causes mirroring every posting of that contact in the users stream." msgstr "" -#: mod/admin.php:933 +#: mod/admin.php:950 msgid "Block multiple registrations" msgstr "" -#: mod/admin.php:933 +#: mod/admin.php:950 msgid "Disallow users to register additional accounts for use as pages." msgstr "" -#: mod/admin.php:934 +#: mod/admin.php:951 msgid "OpenID support" msgstr "" -#: mod/admin.php:934 +#: mod/admin.php:951 msgid "OpenID support for registration and logins." msgstr "" -#: mod/admin.php:935 +#: mod/admin.php:952 msgid "Fullname check" msgstr "" -#: mod/admin.php:935 +#: mod/admin.php:952 msgid "" "Force users to register with a space between firstname and lastname in Full " "name, as an antispam measure" msgstr "" -#: mod/admin.php:936 +#: mod/admin.php:953 msgid "UTF-8 Regular expressions" msgstr "" -#: mod/admin.php:936 +#: mod/admin.php:953 msgid "Use PHP UTF8 regular expressions" msgstr "" -#: mod/admin.php:937 +#: mod/admin.php:954 msgid "Community Page Style" msgstr "" -#: mod/admin.php:937 +#: mod/admin.php:954 msgid "" "Type of community page to show. 'Global community' shows every public " "posting from an open distributed network that arrived on this server." msgstr "" -#: mod/admin.php:938 +#: mod/admin.php:955 msgid "Posts per user on community page" msgstr "" -#: mod/admin.php:938 +#: mod/admin.php:955 msgid "" "The maximum number of posts per user on the community page. (Not valid for " "'Global Community')" msgstr "" -#: mod/admin.php:939 +#: mod/admin.php:956 msgid "Enable OStatus support" msgstr "" -#: mod/admin.php:939 +#: mod/admin.php:956 msgid "" "Provide built-in OStatus (StatusNet, GNU Social etc.) compatibility. All " "communications in OStatus are public, so privacy warnings will be " "occasionally displayed." msgstr "" -#: mod/admin.php:940 +#: mod/admin.php:957 msgid "OStatus conversation completion interval" msgstr "" -#: mod/admin.php:940 +#: mod/admin.php:957 msgid "" "How often shall the poller check for new entries in OStatus conversations? " "This can be a very ressource task." msgstr "" -#: mod/admin.php:941 +#: mod/admin.php:958 +msgid "Only import OStatus threads from our contacts" +msgstr "" + +#: mod/admin.php:958 +msgid "" +"Normally we import every content from our OStatus contacts. With this option " +"we only store threads that are started by a contact that is known on our " +"system." +msgstr "" + +#: mod/admin.php:959 msgid "OStatus support can only be enabled if threading is enabled." msgstr "" -#: mod/admin.php:943 +#: mod/admin.php:961 msgid "" "Diaspora support can't be enabled because Friendica was installed into a sub " "directory." msgstr "" -#: mod/admin.php:944 +#: mod/admin.php:962 msgid "Enable Diaspora support" msgstr "" -#: mod/admin.php:944 +#: mod/admin.php:962 msgid "Provide built-in Diaspora network compatibility." msgstr "" -#: mod/admin.php:945 +#: mod/admin.php:963 msgid "Only allow Friendica contacts" msgstr "" -#: mod/admin.php:945 +#: mod/admin.php:963 msgid "" "All contacts must use Friendica protocols. All other built-in communication " "protocols disabled." msgstr "" -#: mod/admin.php:946 +#: mod/admin.php:964 msgid "Verify SSL" msgstr "" -#: mod/admin.php:946 +#: mod/admin.php:964 msgid "" "If you wish, you can turn on strict certificate checking. This will mean you " "cannot connect (at all) to self-signed SSL sites." msgstr "" -#: mod/admin.php:947 +#: mod/admin.php:965 msgid "Proxy user" msgstr "" -#: mod/admin.php:948 +#: mod/admin.php:966 msgid "Proxy URL" msgstr "" -#: mod/admin.php:949 +#: mod/admin.php:967 msgid "Network timeout" msgstr "" -#: mod/admin.php:949 +#: mod/admin.php:967 msgid "Value is in seconds. Set to 0 for unlimited (not recommended)." msgstr "" -#: mod/admin.php:950 +#: mod/admin.php:968 msgid "Delivery interval" msgstr "" -#: mod/admin.php:950 +#: mod/admin.php:968 msgid "" "Delay background delivery processes by this many seconds to reduce system " "load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 " "for large dedicated servers." msgstr "" -#: mod/admin.php:951 +#: mod/admin.php:969 msgid "Poll interval" msgstr "" -#: mod/admin.php:951 +#: mod/admin.php:969 msgid "" "Delay background polling processes by this many seconds to reduce system " "load. If 0, use delivery interval." msgstr "" -#: mod/admin.php:952 +#: mod/admin.php:970 msgid "Maximum Load Average" msgstr "" -#: mod/admin.php:952 +#: mod/admin.php:970 msgid "" "Maximum system load before delivery and poll processes are deferred - " "default 50." msgstr "" -#: mod/admin.php:953 +#: mod/admin.php:971 msgid "Maximum Load Average (Frontend)" msgstr "" -#: mod/admin.php:953 +#: mod/admin.php:971 msgid "Maximum system load before the frontend quits service - default 50." msgstr "" -#: mod/admin.php:954 +#: mod/admin.php:972 msgid "Maximum table size for optimization" msgstr "" -#: mod/admin.php:954 +#: mod/admin.php:972 msgid "" "Maximum table size (in MB) for the automatic optimization - default 100 MB. " "Enter -1 to disable it." msgstr "" -#: mod/admin.php:955 +#: mod/admin.php:973 msgid "Minimum level of fragmentation" msgstr "" -#: mod/admin.php:955 +#: mod/admin.php:973 msgid "" "Minimum fragmenation level to start the automatic optimization - default " "value is 30%." msgstr "" -#: mod/admin.php:957 +#: mod/admin.php:975 msgid "Periodical check of global contacts" msgstr "" -#: mod/admin.php:957 +#: mod/admin.php:975 msgid "" "If enabled, the global contacts are checked periodically for missing or " "outdated data and the vitality of the contacts and servers." msgstr "" -#: mod/admin.php:958 +#: mod/admin.php:976 msgid "Days between requery" msgstr "" -#: mod/admin.php:958 +#: mod/admin.php:976 msgid "Number of days after which a server is requeried for his contacts." msgstr "" -#: mod/admin.php:959 +#: mod/admin.php:977 msgid "Discover contacts from other servers" msgstr "" -#: mod/admin.php:959 +#: mod/admin.php:977 msgid "" "Periodically query other servers for contacts. You can choose between " "'users': the users on the remote system, 'Global Contacts': active contacts " @@ -2708,32 +2729,32 @@ msgid "" "Global Contacts'." msgstr "" -#: mod/admin.php:960 +#: mod/admin.php:978 msgid "Timeframe for fetching global contacts" msgstr "" -#: mod/admin.php:960 +#: mod/admin.php:978 msgid "" "When the discovery is activated, this value defines the timeframe for the " "activity of the global contacts that are fetched from other servers." msgstr "" -#: mod/admin.php:961 +#: mod/admin.php:979 msgid "Search the local directory" msgstr "" -#: mod/admin.php:961 +#: mod/admin.php:979 msgid "" "Search the local directory instead of the global directory. When searching " "locally, every search will be executed on the global directory in the " "background. This improves the search results when the search is repeated." msgstr "" -#: mod/admin.php:963 +#: mod/admin.php:981 msgid "Publish server information" msgstr "" -#: mod/admin.php:963 +#: mod/admin.php:981 msgid "" "If enabled, general server and usage data will be published. The data " "contains the name and version of the server, number of users with public " @@ -2741,204 +2762,235 @@ msgid "" "href='http://the-federation.info/'>the-federation.info</a> for details." msgstr "" -#: mod/admin.php:965 +#: mod/admin.php:983 msgid "Use MySQL full text engine" msgstr "" -#: mod/admin.php:965 +#: mod/admin.php:983 msgid "" "Activates the full text engine. Speeds up search - but can only search for " "four and more characters." msgstr "" -#: mod/admin.php:966 +#: mod/admin.php:984 msgid "Suppress Language" msgstr "" -#: mod/admin.php:966 +#: mod/admin.php:984 msgid "Suppress language information in meta information about a posting." msgstr "" -#: mod/admin.php:967 +#: mod/admin.php:985 msgid "Suppress Tags" msgstr "" -#: mod/admin.php:967 +#: mod/admin.php:985 msgid "Suppress showing a list of hashtags at the end of the posting." msgstr "" -#: mod/admin.php:968 +#: mod/admin.php:986 msgid "Path to item cache" msgstr "" -#: mod/admin.php:968 +#: mod/admin.php:986 msgid "The item caches buffers generated bbcode and external images." msgstr "" -#: mod/admin.php:969 +#: mod/admin.php:987 msgid "Cache duration in seconds" msgstr "" -#: mod/admin.php:969 +#: mod/admin.php:987 msgid "" "How long should the cache files be hold? Default value is 86400 seconds (One " "day). To disable the item cache, set the value to -1." msgstr "" -#: mod/admin.php:970 +#: mod/admin.php:988 msgid "Maximum numbers of comments per post" msgstr "" -#: mod/admin.php:970 +#: mod/admin.php:988 msgid "How much comments should be shown for each post? Default value is 100." msgstr "" -#: mod/admin.php:971 +#: mod/admin.php:989 msgid "Path for lock file" msgstr "" -#: mod/admin.php:971 +#: mod/admin.php:989 msgid "" "The lock file is used to avoid multiple pollers at one time. Only define a " "folder here." msgstr "" -#: mod/admin.php:972 +#: mod/admin.php:990 msgid "Temp path" msgstr "" -#: mod/admin.php:972 +#: mod/admin.php:990 msgid "" "If you have a restricted system where the webserver can't access the system " "temp path, enter another path here." msgstr "" -#: mod/admin.php:973 +#: mod/admin.php:991 msgid "Base path to installation" msgstr "" -#: mod/admin.php:973 +#: mod/admin.php:991 msgid "" "If the system cannot detect the correct path to your installation, enter the " "correct path here. This setting should only be set if you are using a " "restricted system and symbolic links to your webroot." msgstr "" -#: mod/admin.php:974 +#: mod/admin.php:992 msgid "Disable picture proxy" msgstr "" -#: mod/admin.php:974 +#: mod/admin.php:992 msgid "" "The picture proxy increases performance and privacy. It shouldn't be used on " "systems with very low bandwith." msgstr "" -#: mod/admin.php:975 +#: mod/admin.php:993 msgid "Enable old style pager" msgstr "" -#: mod/admin.php:975 +#: mod/admin.php:993 msgid "" "The old style pager has page numbers but slows down massively the page speed." msgstr "" -#: mod/admin.php:976 +#: mod/admin.php:994 msgid "Only search in tags" msgstr "" -#: mod/admin.php:976 +#: mod/admin.php:994 msgid "On large systems the text search can slow down the system extremely." msgstr "" -#: mod/admin.php:978 +#: mod/admin.php:996 msgid "New base url" msgstr "" -#: mod/admin.php:978 +#: mod/admin.php:996 msgid "" "Change base url for this server. Sends relocate message to all DFRN contacts " "of all users." msgstr "" -#: mod/admin.php:980 +#: mod/admin.php:998 msgid "RINO Encryption" msgstr "" -#: mod/admin.php:980 +#: mod/admin.php:998 msgid "Encryption layer between nodes." msgstr "" -#: mod/admin.php:981 +#: mod/admin.php:999 msgid "Embedly API key" msgstr "" -#: mod/admin.php:981 +#: mod/admin.php:999 msgid "" "<a href='http://embed.ly'>Embedly</a> is used to fetch additional data for " "web pages. This is an optional parameter." msgstr "" -#: mod/admin.php:1010 +#: mod/admin.php:1001 +msgid "Enable 'worker' background processing" +msgstr "" + +#: mod/admin.php:1001 +msgid "" +"The worker background processing limits the number of parallel background " +"jobs to a maximum number and respects the system load." +msgstr "" + +#: mod/admin.php:1002 +msgid "Maximum number of parallel workers" +msgstr "" + +#: mod/admin.php:1002 +msgid "" +"On shared hosters set this to 2. On larger systems, values of 10 are great. " +"Default value is 4." +msgstr "" + +#: mod/admin.php:1003 +msgid "Don't use 'proc_open' with the worker" +msgstr "" + +#: mod/admin.php:1003 +msgid "" +"Enable this if your system doesn't allow the use of 'proc_open'. This can " +"happen on shared hosters. If this is enabled you should increase the " +"frequency of poller calls in your crontab." +msgstr "" + +#: mod/admin.php:1032 msgid "Update has been marked successful" msgstr "" -#: mod/admin.php:1018 -#, php-format -msgid "Database structure update %s was successfully applied." -msgstr "" - -#: mod/admin.php:1021 -#, php-format -msgid "Executing of database structure update %s failed with error: %s" -msgstr "" - -#: mod/admin.php:1033 -#, php-format -msgid "Executing %s failed with error: %s" -msgstr "" - -#: mod/admin.php:1036 -#, php-format -msgid "Update %s was successfully applied." -msgstr "" - #: mod/admin.php:1040 #, php-format +msgid "Database structure update %s was successfully applied." +msgstr "" + +#: mod/admin.php:1043 +#, php-format +msgid "Executing of database structure update %s failed with error: %s" +msgstr "" + +#: mod/admin.php:1055 +#, php-format +msgid "Executing %s failed with error: %s" +msgstr "" + +#: mod/admin.php:1058 +#, php-format +msgid "Update %s was successfully applied." +msgstr "" + +#: mod/admin.php:1062 +#, php-format msgid "Update %s did not return a status. Unknown if it succeeded." msgstr "" -#: mod/admin.php:1042 +#: mod/admin.php:1064 #, php-format msgid "There was no additional update function %s that needed to be called." msgstr "" -#: mod/admin.php:1061 +#: mod/admin.php:1083 msgid "No failed updates." msgstr "" -#: mod/admin.php:1062 +#: mod/admin.php:1084 msgid "Check database structure" msgstr "" -#: mod/admin.php:1067 +#: mod/admin.php:1089 msgid "Failed Updates" msgstr "" -#: mod/admin.php:1068 +#: mod/admin.php:1090 msgid "" "This does not include updates prior to 1139, which did not return a status." msgstr "" -#: mod/admin.php:1069 +#: mod/admin.php:1091 msgid "Mark success (if update was manually applied)" msgstr "" -#: mod/admin.php:1070 +#: mod/admin.php:1092 msgid "Attempt to execute this update step automatically" msgstr "" -#: mod/admin.php:1102 +#: mod/admin.php:1124 #, php-format msgid "" "\n" @@ -2946,7 +2998,7 @@ msgid "" "\t\t\t\tthe administrator of %2$s has set up an account for you." msgstr "" -#: mod/admin.php:1105 +#: mod/admin.php:1127 #, php-format msgid "" "\n" @@ -2982,168 +3034,168 @@ msgid "" "\t\t\tThank you and welcome to %4$s." msgstr "" -#: mod/admin.php:1137 include/user.php:423 +#: mod/admin.php:1159 include/user.php:423 #, php-format msgid "Registration details for %s" msgstr "" -#: mod/admin.php:1149 +#: mod/admin.php:1171 #, php-format msgid "%s user blocked/unblocked" msgid_plural "%s users blocked/unblocked" msgstr[0] "" msgstr[1] "" -#: mod/admin.php:1156 +#: mod/admin.php:1178 #, php-format msgid "%s user deleted" msgid_plural "%s users deleted" msgstr[0] "" msgstr[1] "" -#: mod/admin.php:1203 +#: mod/admin.php:1225 #, php-format msgid "User '%s' deleted" msgstr "" -#: mod/admin.php:1211 +#: mod/admin.php:1233 #, php-format msgid "User '%s' unblocked" msgstr "" -#: mod/admin.php:1211 +#: mod/admin.php:1233 #, php-format msgid "User '%s' blocked" msgstr "" -#: mod/admin.php:1302 +#: mod/admin.php:1324 msgid "Add User" msgstr "" -#: mod/admin.php:1303 +#: mod/admin.php:1325 msgid "select all" msgstr "" -#: mod/admin.php:1304 +#: mod/admin.php:1326 msgid "User registrations waiting for confirm" msgstr "" -#: mod/admin.php:1305 +#: mod/admin.php:1327 msgid "User waiting for permanent deletion" msgstr "" -#: mod/admin.php:1306 +#: mod/admin.php:1328 msgid "Request date" msgstr "" -#: mod/admin.php:1306 mod/admin.php:1318 mod/admin.php:1319 mod/admin.php:1334 +#: mod/admin.php:1328 mod/admin.php:1340 mod/admin.php:1341 mod/admin.php:1356 #: include/contact_selectors.php:79 include/contact_selectors.php:86 msgid "Email" msgstr "" -#: mod/admin.php:1307 +#: mod/admin.php:1329 msgid "No registrations." msgstr "" -#: mod/admin.php:1309 +#: mod/admin.php:1331 msgid "Deny" msgstr "" -#: mod/admin.php:1313 +#: mod/admin.php:1335 msgid "Site admin" msgstr "" -#: mod/admin.php:1314 +#: mod/admin.php:1336 msgid "Account expired" msgstr "" -#: mod/admin.php:1317 +#: mod/admin.php:1339 msgid "New User" msgstr "" -#: mod/admin.php:1318 mod/admin.php:1319 +#: mod/admin.php:1340 mod/admin.php:1341 msgid "Register date" msgstr "" -#: mod/admin.php:1318 mod/admin.php:1319 +#: mod/admin.php:1340 mod/admin.php:1341 msgid "Last login" msgstr "" -#: mod/admin.php:1318 mod/admin.php:1319 +#: mod/admin.php:1340 mod/admin.php:1341 msgid "Last item" msgstr "" -#: mod/admin.php:1318 +#: mod/admin.php:1340 msgid "Deleted since" msgstr "" -#: mod/admin.php:1319 mod/settings.php:41 +#: mod/admin.php:1341 mod/settings.php:41 msgid "Account" msgstr "" -#: mod/admin.php:1321 +#: mod/admin.php:1343 msgid "" "Selected users will be deleted!\\n\\nEverything these users had posted on " "this site will be permanently deleted!\\n\\nAre you sure?" msgstr "" -#: mod/admin.php:1322 +#: mod/admin.php:1344 msgid "" "The user {0} will be deleted!\\n\\nEverything this user has posted on this " "site will be permanently deleted!\\n\\nAre you sure?" msgstr "" -#: mod/admin.php:1332 +#: mod/admin.php:1354 msgid "Name of the new user." msgstr "" -#: mod/admin.php:1333 +#: mod/admin.php:1355 msgid "Nickname" msgstr "" -#: mod/admin.php:1333 +#: mod/admin.php:1355 msgid "Nickname of the new user." msgstr "" -#: mod/admin.php:1334 +#: mod/admin.php:1356 msgid "Email address of the new user." msgstr "" -#: mod/admin.php:1377 +#: mod/admin.php:1399 #, php-format msgid "Plugin %s disabled." msgstr "" -#: mod/admin.php:1381 +#: mod/admin.php:1403 #, php-format msgid "Plugin %s enabled." msgstr "" -#: mod/admin.php:1392 mod/admin.php:1628 +#: mod/admin.php:1414 mod/admin.php:1650 msgid "Disable" msgstr "" -#: mod/admin.php:1394 mod/admin.php:1630 +#: mod/admin.php:1416 mod/admin.php:1652 msgid "Enable" msgstr "" -#: mod/admin.php:1417 mod/admin.php:1675 +#: mod/admin.php:1439 mod/admin.php:1697 msgid "Toggle" msgstr "" -#: mod/admin.php:1425 mod/admin.php:1684 +#: mod/admin.php:1447 mod/admin.php:1706 msgid "Author: " msgstr "" -#: mod/admin.php:1426 mod/admin.php:1685 +#: mod/admin.php:1448 mod/admin.php:1707 msgid "Maintainer: " msgstr "" -#: mod/admin.php:1478 +#: mod/admin.php:1500 msgid "Reload active plugins" msgstr "" -#: mod/admin.php:1483 +#: mod/admin.php:1505 #, php-format msgid "" "There are currently no plugins available on your node. You can find the " @@ -3151,62 +3203,62 @@ msgid "" "in the open plugin registry at %2$s" msgstr "" -#: mod/admin.php:1588 +#: mod/admin.php:1610 msgid "No themes found." msgstr "" -#: mod/admin.php:1666 +#: mod/admin.php:1688 msgid "Screenshot" msgstr "" -#: mod/admin.php:1726 +#: mod/admin.php:1748 msgid "Reload active themes" msgstr "" -#: mod/admin.php:1731 +#: mod/admin.php:1753 #, php-format msgid "No themes found on the system. They should be paced in %1$s" msgstr "" -#: mod/admin.php:1732 +#: mod/admin.php:1754 msgid "[Experimental]" msgstr "" -#: mod/admin.php:1733 +#: mod/admin.php:1755 msgid "[Unsupported]" msgstr "" -#: mod/admin.php:1757 +#: mod/admin.php:1779 msgid "Log settings updated." msgstr "" -#: mod/admin.php:1794 +#: mod/admin.php:1816 msgid "Clear" msgstr "" -#: mod/admin.php:1799 +#: mod/admin.php:1821 msgid "Enable Debugging" msgstr "" -#: mod/admin.php:1800 +#: mod/admin.php:1822 msgid "Log file" msgstr "" -#: mod/admin.php:1800 +#: mod/admin.php:1822 msgid "" "Must be writable by web server. Relative to your Friendica top-level " "directory." msgstr "" -#: mod/admin.php:1801 +#: mod/admin.php:1823 msgid "Log level" msgstr "" -#: mod/admin.php:1804 +#: mod/admin.php:1826 msgid "PHP logging" msgstr "" -#: mod/admin.php:1805 +#: mod/admin.php:1827 msgid "" "To enable logging of PHP errors and warnings you can add the following to " "the .htconfig.php file of your installation. The filename set in the " @@ -3215,20 +3267,20 @@ msgid "" "'display_errors' is to enable these options, set to '0' to disable them." msgstr "" -#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759 +#: mod/admin.php:1953 mod/admin.php:1954 mod/settings.php:760 msgid "Off" msgstr "" -#: mod/admin.php:1931 mod/admin.php:1932 mod/settings.php:759 +#: mod/admin.php:1953 mod/admin.php:1954 mod/settings.php:760 msgid "On" msgstr "" -#: mod/admin.php:1932 +#: mod/admin.php:1954 #, php-format msgid "Lock feature %s" msgstr "" -#: mod/admin.php:1940 +#: mod/admin.php:1962 msgid "Manage Additional Features" msgstr "" @@ -3245,7 +3297,7 @@ msgstr "" msgid "Saved Searches" msgstr "" -#: mod/network.php:201 include/group.php:293 +#: mod/network.php:201 include/group.php:296 msgid "add" msgstr "" @@ -3362,31 +3414,31 @@ msgstr "" msgid "Sat" msgstr "" -#: mod/events.php:208 mod/settings.php:948 include/text.php:1274 +#: mod/events.php:208 mod/settings.php:952 include/text.php:1245 msgid "Sunday" msgstr "" -#: mod/events.php:209 mod/settings.php:948 include/text.php:1274 +#: mod/events.php:209 mod/settings.php:952 include/text.php:1245 msgid "Monday" msgstr "" -#: mod/events.php:210 include/text.php:1274 +#: mod/events.php:210 include/text.php:1245 msgid "Tuesday" msgstr "" -#: mod/events.php:211 include/text.php:1274 +#: mod/events.php:211 include/text.php:1245 msgid "Wednesday" msgstr "" -#: mod/events.php:212 include/text.php:1274 +#: mod/events.php:212 include/text.php:1245 msgid "Thursday" msgstr "" -#: mod/events.php:213 include/text.php:1274 +#: mod/events.php:213 include/text.php:1245 msgid "Friday" msgstr "" -#: mod/events.php:214 include/text.php:1274 +#: mod/events.php:214 include/text.php:1245 msgid "Saturday" msgstr "" @@ -3406,7 +3458,7 @@ msgstr "" msgid "Apr" msgstr "" -#: mod/events.php:219 mod/events.php:231 include/text.php:1278 +#: mod/events.php:219 mod/events.php:231 include/text.php:1249 msgid "May" msgstr "" @@ -3438,47 +3490,47 @@ msgstr "" msgid "Dec" msgstr "" -#: mod/events.php:227 include/text.php:1278 +#: mod/events.php:227 include/text.php:1249 msgid "January" msgstr "" -#: mod/events.php:228 include/text.php:1278 +#: mod/events.php:228 include/text.php:1249 msgid "February" msgstr "" -#: mod/events.php:229 include/text.php:1278 +#: mod/events.php:229 include/text.php:1249 msgid "March" msgstr "" -#: mod/events.php:230 include/text.php:1278 +#: mod/events.php:230 include/text.php:1249 msgid "April" msgstr "" -#: mod/events.php:232 include/text.php:1278 +#: mod/events.php:232 include/text.php:1249 msgid "June" msgstr "" -#: mod/events.php:233 include/text.php:1278 +#: mod/events.php:233 include/text.php:1249 msgid "July" msgstr "" -#: mod/events.php:234 include/text.php:1278 +#: mod/events.php:234 include/text.php:1249 msgid "August" msgstr "" -#: mod/events.php:235 include/text.php:1278 +#: mod/events.php:235 include/text.php:1249 msgid "September" msgstr "" -#: mod/events.php:236 include/text.php:1278 +#: mod/events.php:236 include/text.php:1249 msgid "October" msgstr "" -#: mod/events.php:237 include/text.php:1278 +#: mod/events.php:237 include/text.php:1249 msgid "November" msgstr "" -#: mod/events.php:238 include/text.php:1278 +#: mod/events.php:238 include/text.php:1249 msgid "December" msgstr "" @@ -3486,15 +3538,15 @@ msgstr "" msgid "today" msgstr "" -#: mod/events.php:240 include/datetime.php:288 +#: mod/events.php:240 include/datetime.php:344 msgid "month" msgstr "" -#: mod/events.php:241 include/datetime.php:289 +#: mod/events.php:241 include/datetime.php:345 msgid "week" msgstr "" -#: mod/events.php:242 include/datetime.php:290 +#: mod/events.php:242 include/datetime.php:346 msgid "day" msgstr "" @@ -3506,11 +3558,11 @@ msgstr "" msgid "Edit event" msgstr "" -#: mod/events.php:421 include/text.php:1728 include/text.php:1735 +#: mod/events.php:421 include/text.php:1651 include/text.php:1658 msgid "link to source" msgstr "" -#: mod/events.php:456 include/identity.php:722 include/nav.php:79 +#: mod/events.php:456 include/identity.php:723 include/nav.php:79 #: include/nav.php:140 view/theme/diabook/theme.php:127 msgid "Events" msgstr "" @@ -3568,7 +3620,7 @@ msgid "Share this event" msgstr "" #: mod/events.php:572 mod/content.php:721 mod/editpost.php:145 -#: mod/photos.php:1631 mod/photos.php:1679 mod/photos.php:1767 +#: mod/photos.php:1617 mod/photos.php:1665 mod/photos.php:1753 #: object/Item.php:719 include/conversation.php:1216 msgid "Preview" msgstr "" @@ -3584,7 +3636,7 @@ msgid "" "code or the translation of Friendica. Thank you all!" msgstr "" -#: mod/content.php:439 mod/content.php:742 mod/photos.php:1722 +#: mod/content.php:439 mod/content.php:742 mod/photos.php:1708 #: object/Item.php:133 include/conversation.php:634 msgid "Select" msgstr "" @@ -3613,23 +3665,23 @@ msgstr[0] "" msgstr[1] "" #: mod/content.php:607 object/Item.php:421 object/Item.php:434 -#: include/text.php:2004 +#: include/text.php:1927 msgid "comment" msgid_plural "comments" msgstr[0] "" msgstr[1] "" -#: mod/content.php:608 boot.php:870 object/Item.php:422 -#: include/contact_widgets.php:242 include/forums.php:110 -#: include/items.php:5207 view/theme/vier/theme.php:264 +#: mod/content.php:608 boot.php:872 object/Item.php:422 +#: include/contact_widgets.php:242 include/ForumManager.php:117 +#: include/items.php:2113 view/theme/vier/theme.php:260 msgid "show more" msgstr "" -#: mod/content.php:622 mod/photos.php:1418 object/Item.php:117 +#: mod/content.php:622 mod/photos.php:1404 object/Item.php:117 msgid "Private Message" msgstr "" -#: mod/content.php:686 mod/photos.php:1607 object/Item.php:253 +#: mod/content.php:686 mod/photos.php:1593 object/Item.php:253 msgid "I like this (toggle)" msgstr "" @@ -3637,7 +3689,7 @@ msgstr "" msgid "like" msgstr "" -#: mod/content.php:687 mod/photos.php:1608 object/Item.php:254 +#: mod/content.php:687 mod/photos.php:1594 object/Item.php:254 msgid "I don't like this (toggle)" msgstr "" @@ -3653,13 +3705,13 @@ msgstr "" msgid "share" msgstr "" -#: mod/content.php:709 mod/photos.php:1627 mod/photos.php:1675 -#: mod/photos.php:1763 object/Item.php:707 +#: mod/content.php:709 mod/photos.php:1613 mod/photos.php:1661 +#: mod/photos.php:1749 object/Item.php:707 msgid "This is you" msgstr "" -#: mod/content.php:711 mod/photos.php:1629 mod/photos.php:1677 -#: mod/photos.php:1765 boot.php:869 object/Item.php:393 object/Item.php:709 +#: mod/content.php:711 mod/photos.php:1615 mod/photos.php:1663 +#: mod/photos.php:1751 boot.php:871 object/Item.php:393 object/Item.php:709 msgid "Comment" msgstr "" @@ -3695,7 +3747,7 @@ msgstr "" msgid "Video" msgstr "" -#: mod/content.php:730 mod/settings.php:721 object/Item.php:122 +#: mod/content.php:730 mod/settings.php:722 object/Item.php:122 #: object/Item.php:124 msgid "Edit" msgstr "" @@ -4094,19 +4146,19 @@ msgstr "" msgid "Help:" msgstr "" -#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:302 +#: mod/help.php:47 include/nav.php:113 view/theme/vier/theme.php:298 msgid "Help" msgstr "" -#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:270 +#: mod/help.php:53 mod/p.php:16 mod/p.php:25 index.php:271 msgid "Not Found" msgstr "" -#: mod/help.php:56 index.php:273 +#: mod/help.php:56 index.php:274 msgid "Page not found." msgstr "" -#: mod/dfrn_poll.php:103 mod/dfrn_poll.php:536 +#: mod/dfrn_poll.php:101 mod/dfrn_poll.php:534 #, php-format msgid "%1$s welcomes %2$s" msgstr "" @@ -4170,7 +4222,7 @@ msgstr "" msgid "Display" msgstr "" -#: mod/settings.php:65 mod/settings.php:864 +#: mod/settings.php:65 mod/settings.php:868 msgid "Social Networks" msgstr "" @@ -4182,7 +4234,7 @@ msgstr "" msgid "Connected apps" msgstr "" -#: mod/settings.php:93 mod/uexport.php:85 +#: mod/settings.php:93 mod/uexport.php:37 msgid "Export personal data" msgstr "" @@ -4194,685 +4246,689 @@ msgstr "" msgid "Missing some important data!" msgstr "" -#: mod/settings.php:266 +#: mod/settings.php:267 msgid "Failed to connect with email account using the settings provided." msgstr "" -#: mod/settings.php:271 +#: mod/settings.php:272 msgid "Email settings updated." msgstr "" -#: mod/settings.php:286 +#: mod/settings.php:287 msgid "Features updated" msgstr "" -#: mod/settings.php:353 +#: mod/settings.php:354 msgid "Relocate message has been send to your contacts" msgstr "" -#: mod/settings.php:367 include/user.php:39 +#: mod/settings.php:368 include/user.php:39 msgid "Passwords do not match. Password unchanged." msgstr "" -#: mod/settings.php:372 +#: mod/settings.php:373 msgid "Empty passwords are not allowed. Password unchanged." msgstr "" -#: mod/settings.php:380 +#: mod/settings.php:381 msgid "Wrong password." msgstr "" -#: mod/settings.php:391 +#: mod/settings.php:392 msgid "Password changed." msgstr "" -#: mod/settings.php:393 +#: mod/settings.php:394 msgid "Password update failed. Please try again." msgstr "" -#: mod/settings.php:462 +#: mod/settings.php:463 msgid " Please use a shorter name." msgstr "" -#: mod/settings.php:464 +#: mod/settings.php:465 msgid " Name too short." msgstr "" -#: mod/settings.php:473 +#: mod/settings.php:474 msgid "Wrong Password" msgstr "" -#: mod/settings.php:478 +#: mod/settings.php:479 msgid " Not valid email." msgstr "" -#: mod/settings.php:484 +#: mod/settings.php:485 msgid " Cannot change to that email." msgstr "" -#: mod/settings.php:540 +#: mod/settings.php:541 msgid "Private forum has no privacy permissions. Using default privacy group." msgstr "" -#: mod/settings.php:544 +#: mod/settings.php:545 msgid "Private forum has no privacy permissions and no default privacy group." msgstr "" -#: mod/settings.php:583 +#: mod/settings.php:584 msgid "Settings updated." msgstr "" -#: mod/settings.php:658 mod/settings.php:684 mod/settings.php:720 +#: mod/settings.php:659 mod/settings.php:685 mod/settings.php:721 msgid "Add application" msgstr "" -#: mod/settings.php:662 mod/settings.php:688 +#: mod/settings.php:663 mod/settings.php:689 msgid "Consumer Key" msgstr "" -#: mod/settings.php:663 mod/settings.php:689 +#: mod/settings.php:664 mod/settings.php:690 msgid "Consumer Secret" msgstr "" -#: mod/settings.php:664 mod/settings.php:690 +#: mod/settings.php:665 mod/settings.php:691 msgid "Redirect" msgstr "" -#: mod/settings.php:665 mod/settings.php:691 +#: mod/settings.php:666 mod/settings.php:692 msgid "Icon url" msgstr "" -#: mod/settings.php:676 +#: mod/settings.php:677 msgid "You can't edit this application." msgstr "" -#: mod/settings.php:719 +#: mod/settings.php:720 msgid "Connected Apps" msgstr "" -#: mod/settings.php:723 +#: mod/settings.php:724 msgid "Client key starts with" msgstr "" -#: mod/settings.php:724 +#: mod/settings.php:725 msgid "No name" msgstr "" -#: mod/settings.php:725 +#: mod/settings.php:726 msgid "Remove authorization" msgstr "" -#: mod/settings.php:737 +#: mod/settings.php:738 msgid "No Plugin settings configured" msgstr "" -#: mod/settings.php:745 +#: mod/settings.php:746 msgid "Plugin Settings" msgstr "" -#: mod/settings.php:767 +#: mod/settings.php:768 msgid "Additional Features" msgstr "" -#: mod/settings.php:777 mod/settings.php:781 +#: mod/settings.php:778 mod/settings.php:782 msgid "General Social Media Settings" msgstr "" -#: mod/settings.php:787 +#: mod/settings.php:788 msgid "Disable intelligent shortening" msgstr "" -#: mod/settings.php:789 +#: mod/settings.php:790 msgid "" "Normally the system tries to find the best link to add to shortened posts. " "If this option is enabled then every shortened post will always point to the " "original friendica post." msgstr "" -#: mod/settings.php:795 +#: mod/settings.php:796 msgid "Automatically follow any GNU Social (OStatus) followers/mentioners" msgstr "" -#: mod/settings.php:797 +#: mod/settings.php:798 msgid "" "If you receive a message from an unknown OStatus user, this option decides " "what to do. If it is checked, a new contact will be created for every " "unknown user." msgstr "" -#: mod/settings.php:806 +#: mod/settings.php:804 +msgid "Default group for OStatus contacts" +msgstr "" + +#: mod/settings.php:810 msgid "Your legacy GNU Social account" msgstr "" -#: mod/settings.php:808 +#: mod/settings.php:812 msgid "" "If you enter your old GNU Social/Statusnet account name here (in the format " "user@domain.tld), your contacts will be added automatically. The field will " "be emptied when done." msgstr "" -#: mod/settings.php:811 +#: mod/settings.php:815 msgid "Repair OStatus subscriptions" msgstr "" -#: mod/settings.php:820 mod/settings.php:821 +#: mod/settings.php:824 mod/settings.php:825 #, php-format msgid "Built-in support for %s connectivity is %s" msgstr "" -#: mod/settings.php:820 mod/dfrn_request.php:865 +#: mod/settings.php:824 mod/dfrn_request.php:863 #: include/contact_selectors.php:80 msgid "Diaspora" msgstr "" -#: mod/settings.php:820 mod/settings.php:821 +#: mod/settings.php:824 mod/settings.php:825 msgid "enabled" msgstr "" -#: mod/settings.php:820 mod/settings.php:821 +#: mod/settings.php:824 mod/settings.php:825 msgid "disabled" msgstr "" -#: mod/settings.php:821 +#: mod/settings.php:825 msgid "GNU Social (OStatus)" msgstr "" -#: mod/settings.php:857 +#: mod/settings.php:861 msgid "Email access is disabled on this site." msgstr "" -#: mod/settings.php:869 +#: mod/settings.php:873 msgid "Email/Mailbox Setup" msgstr "" -#: mod/settings.php:870 +#: mod/settings.php:874 msgid "" "If you wish to communicate with email contacts using this service " "(optional), please specify how to connect to your mailbox." msgstr "" -#: mod/settings.php:871 +#: mod/settings.php:875 msgid "Last successful email check:" msgstr "" -#: mod/settings.php:873 +#: mod/settings.php:877 msgid "IMAP server name:" msgstr "" -#: mod/settings.php:874 +#: mod/settings.php:878 msgid "IMAP port:" msgstr "" -#: mod/settings.php:875 +#: mod/settings.php:879 msgid "Security:" msgstr "" -#: mod/settings.php:875 mod/settings.php:880 +#: mod/settings.php:879 mod/settings.php:884 msgid "None" msgstr "" -#: mod/settings.php:876 +#: mod/settings.php:880 msgid "Email login name:" msgstr "" -#: mod/settings.php:877 +#: mod/settings.php:881 msgid "Email password:" msgstr "" -#: mod/settings.php:878 +#: mod/settings.php:882 msgid "Reply-to address:" msgstr "" -#: mod/settings.php:879 +#: mod/settings.php:883 msgid "Send public posts to all email contacts:" msgstr "" -#: mod/settings.php:880 +#: mod/settings.php:884 msgid "Action after import:" msgstr "" -#: mod/settings.php:880 +#: mod/settings.php:884 msgid "Mark as seen" msgstr "" -#: mod/settings.php:880 +#: mod/settings.php:884 msgid "Move to folder" msgstr "" -#: mod/settings.php:881 +#: mod/settings.php:885 msgid "Move to folder:" msgstr "" -#: mod/settings.php:967 +#: mod/settings.php:971 msgid "Display Settings" msgstr "" -#: mod/settings.php:973 mod/settings.php:991 +#: mod/settings.php:977 mod/settings.php:995 msgid "Display Theme:" msgstr "" -#: mod/settings.php:974 +#: mod/settings.php:978 msgid "Mobile Theme:" msgstr "" -#: mod/settings.php:975 +#: mod/settings.php:979 msgid "Update browser every xx seconds" msgstr "" -#: mod/settings.php:975 +#: mod/settings.php:979 msgid "Minimum of 10 seconds. Enter -1 to disable it." msgstr "" -#: mod/settings.php:976 +#: mod/settings.php:980 msgid "Number of items to display per page:" msgstr "" -#: mod/settings.php:976 mod/settings.php:977 +#: mod/settings.php:980 mod/settings.php:981 msgid "Maximum of 100 items" msgstr "" -#: mod/settings.php:977 +#: mod/settings.php:981 msgid "Number of items to display per page when viewed from mobile device:" msgstr "" -#: mod/settings.php:978 +#: mod/settings.php:982 msgid "Don't show emoticons" msgstr "" -#: mod/settings.php:979 +#: mod/settings.php:983 msgid "Calendar" msgstr "" -#: mod/settings.php:980 +#: mod/settings.php:984 msgid "Beginning of week:" msgstr "" -#: mod/settings.php:981 +#: mod/settings.php:985 msgid "Don't show notices" msgstr "" -#: mod/settings.php:982 +#: mod/settings.php:986 msgid "Infinite scroll" msgstr "" -#: mod/settings.php:983 +#: mod/settings.php:987 msgid "Automatic updates only at the top of the network page" msgstr "" -#: mod/settings.php:985 view/theme/cleanzero/config.php:82 +#: mod/settings.php:989 view/theme/cleanzero/config.php:82 #: view/theme/dispy/config.php:72 view/theme/quattro/config.php:66 #: view/theme/diabook/config.php:150 view/theme/vier/config.php:109 #: view/theme/duepuntozero/config.php:61 msgid "Theme settings" msgstr "" -#: mod/settings.php:1062 +#: mod/settings.php:1066 msgid "User Types" msgstr "" -#: mod/settings.php:1063 +#: mod/settings.php:1067 msgid "Community Types" msgstr "" -#: mod/settings.php:1064 +#: mod/settings.php:1068 msgid "Normal Account Page" msgstr "" -#: mod/settings.php:1065 +#: mod/settings.php:1069 msgid "This account is a normal personal profile" msgstr "" -#: mod/settings.php:1068 +#: mod/settings.php:1072 msgid "Soapbox Page" msgstr "" -#: mod/settings.php:1069 +#: mod/settings.php:1073 msgid "Automatically approve all connection/friend requests as read-only fans" msgstr "" -#: mod/settings.php:1072 +#: mod/settings.php:1076 msgid "Community Forum/Celebrity Account" msgstr "" -#: mod/settings.php:1073 +#: mod/settings.php:1077 msgid "Automatically approve all connection/friend requests as read-write fans" msgstr "" -#: mod/settings.php:1076 +#: mod/settings.php:1080 msgid "Automatic Friend Page" msgstr "" -#: mod/settings.php:1077 +#: mod/settings.php:1081 msgid "Automatically approve all connection/friend requests as friends" msgstr "" -#: mod/settings.php:1080 +#: mod/settings.php:1084 msgid "Private Forum [Experimental]" msgstr "" -#: mod/settings.php:1081 +#: mod/settings.php:1085 msgid "Private forum - approved members only" msgstr "" -#: mod/settings.php:1093 +#: mod/settings.php:1097 msgid "OpenID:" msgstr "" -#: mod/settings.php:1093 +#: mod/settings.php:1097 msgid "(Optional) Allow this OpenID to login to this account." msgstr "" -#: mod/settings.php:1103 +#: mod/settings.php:1107 msgid "Publish your default profile in your local site directory?" msgstr "" -#: mod/settings.php:1109 +#: mod/settings.php:1113 msgid "Publish your default profile in the global social directory?" msgstr "" -#: mod/settings.php:1117 +#: mod/settings.php:1121 msgid "Hide your contact/friend list from viewers of your default profile?" msgstr "" -#: mod/settings.php:1121 include/acl_selectors.php:331 +#: mod/settings.php:1125 include/acl_selectors.php:331 msgid "Hide your profile details from unknown viewers?" msgstr "" -#: mod/settings.php:1121 +#: mod/settings.php:1125 msgid "" "If enabled, posting public messages to Diaspora and other networks isn't " "possible." msgstr "" -#: mod/settings.php:1126 +#: mod/settings.php:1130 msgid "Allow friends to post to your profile page?" msgstr "" -#: mod/settings.php:1132 +#: mod/settings.php:1136 msgid "Allow friends to tag your posts?" msgstr "" -#: mod/settings.php:1138 +#: mod/settings.php:1142 msgid "Allow us to suggest you as a potential friend to new members?" msgstr "" -#: mod/settings.php:1144 +#: mod/settings.php:1148 msgid "Permit unknown people to send you private mail?" msgstr "" -#: mod/settings.php:1152 +#: mod/settings.php:1156 msgid "Profile is <strong>not published</strong>." msgstr "" -#: mod/settings.php:1160 +#: mod/settings.php:1164 #, php-format msgid "Your Identity Address is <strong>'%s'</strong> or '%s'." msgstr "" -#: mod/settings.php:1167 +#: mod/settings.php:1171 msgid "Automatically expire posts after this many days:" msgstr "" -#: mod/settings.php:1167 +#: mod/settings.php:1171 msgid "If empty, posts will not expire. Expired posts will be deleted" msgstr "" -#: mod/settings.php:1168 +#: mod/settings.php:1172 msgid "Advanced expiration settings" msgstr "" -#: mod/settings.php:1169 +#: mod/settings.php:1173 msgid "Advanced Expiration" msgstr "" -#: mod/settings.php:1170 +#: mod/settings.php:1174 msgid "Expire posts:" msgstr "" -#: mod/settings.php:1171 +#: mod/settings.php:1175 msgid "Expire personal notes:" msgstr "" -#: mod/settings.php:1172 +#: mod/settings.php:1176 msgid "Expire starred posts:" msgstr "" -#: mod/settings.php:1173 +#: mod/settings.php:1177 msgid "Expire photos:" msgstr "" -#: mod/settings.php:1174 +#: mod/settings.php:1178 msgid "Only expire posts by others:" msgstr "" -#: mod/settings.php:1202 +#: mod/settings.php:1206 msgid "Account Settings" msgstr "" -#: mod/settings.php:1210 +#: mod/settings.php:1214 msgid "Password Settings" msgstr "" -#: mod/settings.php:1211 mod/register.php:274 +#: mod/settings.php:1215 mod/register.php:274 msgid "New Password:" msgstr "" -#: mod/settings.php:1212 mod/register.php:275 +#: mod/settings.php:1216 mod/register.php:275 msgid "Confirm:" msgstr "" -#: mod/settings.php:1212 +#: mod/settings.php:1216 msgid "Leave password fields blank unless changing" msgstr "" -#: mod/settings.php:1213 +#: mod/settings.php:1217 msgid "Current Password:" msgstr "" -#: mod/settings.php:1213 mod/settings.php:1214 +#: mod/settings.php:1217 mod/settings.php:1218 msgid "Your current password to confirm the changes" msgstr "" -#: mod/settings.php:1214 +#: mod/settings.php:1218 msgid "Password:" msgstr "" -#: mod/settings.php:1218 +#: mod/settings.php:1222 msgid "Basic Settings" msgstr "" -#: mod/settings.php:1219 include/identity.php:588 +#: mod/settings.php:1223 include/identity.php:589 msgid "Full Name:" msgstr "" -#: mod/settings.php:1220 +#: mod/settings.php:1224 msgid "Email Address:" msgstr "" -#: mod/settings.php:1221 +#: mod/settings.php:1225 msgid "Your Timezone:" msgstr "" -#: mod/settings.php:1222 +#: mod/settings.php:1226 msgid "Your Language:" msgstr "" -#: mod/settings.php:1222 +#: mod/settings.php:1226 msgid "" "Set the language we use to show you friendica interface and to send you " "emails" msgstr "" -#: mod/settings.php:1223 +#: mod/settings.php:1227 msgid "Default Post Location:" msgstr "" -#: mod/settings.php:1224 +#: mod/settings.php:1228 msgid "Use Browser Location:" msgstr "" -#: mod/settings.php:1227 +#: mod/settings.php:1231 msgid "Security and Privacy Settings" msgstr "" -#: mod/settings.php:1229 +#: mod/settings.php:1233 msgid "Maximum Friend Requests/Day:" msgstr "" -#: mod/settings.php:1229 mod/settings.php:1259 +#: mod/settings.php:1233 mod/settings.php:1263 msgid "(to prevent spam abuse)" msgstr "" -#: mod/settings.php:1230 +#: mod/settings.php:1234 msgid "Default Post Permissions" msgstr "" -#: mod/settings.php:1231 +#: mod/settings.php:1235 msgid "(click to open/close)" msgstr "" -#: mod/settings.php:1240 mod/photos.php:1199 mod/photos.php:1584 +#: mod/settings.php:1244 mod/photos.php:1185 mod/photos.php:1570 msgid "Show to Groups" msgstr "" -#: mod/settings.php:1241 mod/photos.php:1200 mod/photos.php:1585 +#: mod/settings.php:1245 mod/photos.php:1186 mod/photos.php:1571 msgid "Show to Contacts" msgstr "" -#: mod/settings.php:1242 +#: mod/settings.php:1246 msgid "Default Private Post" msgstr "" -#: mod/settings.php:1243 +#: mod/settings.php:1247 msgid "Default Public Post" msgstr "" -#: mod/settings.php:1247 +#: mod/settings.php:1251 msgid "Default Permissions for New Posts" msgstr "" -#: mod/settings.php:1259 +#: mod/settings.php:1263 msgid "Maximum private messages per day from unknown people:" msgstr "" -#: mod/settings.php:1262 +#: mod/settings.php:1266 msgid "Notification Settings" msgstr "" -#: mod/settings.php:1263 +#: mod/settings.php:1267 msgid "By default post a status message when:" msgstr "" -#: mod/settings.php:1264 +#: mod/settings.php:1268 msgid "accepting a friend request" msgstr "" -#: mod/settings.php:1265 +#: mod/settings.php:1269 msgid "joining a forum/community" msgstr "" -#: mod/settings.php:1266 +#: mod/settings.php:1270 msgid "making an <em>interesting</em> profile change" msgstr "" -#: mod/settings.php:1267 +#: mod/settings.php:1271 msgid "Send a notification email when:" msgstr "" -#: mod/settings.php:1268 +#: mod/settings.php:1272 msgid "You receive an introduction" msgstr "" -#: mod/settings.php:1269 +#: mod/settings.php:1273 msgid "Your introductions are confirmed" msgstr "" -#: mod/settings.php:1270 +#: mod/settings.php:1274 msgid "Someone writes on your profile wall" msgstr "" -#: mod/settings.php:1271 +#: mod/settings.php:1275 msgid "Someone writes a followup comment" msgstr "" -#: mod/settings.php:1272 +#: mod/settings.php:1276 msgid "You receive a private message" msgstr "" -#: mod/settings.php:1273 +#: mod/settings.php:1277 msgid "You receive a friend suggestion" msgstr "" -#: mod/settings.php:1274 +#: mod/settings.php:1278 msgid "You are tagged in a post" msgstr "" -#: mod/settings.php:1275 +#: mod/settings.php:1279 msgid "You are poked/prodded/etc. in a post" msgstr "" -#: mod/settings.php:1277 +#: mod/settings.php:1281 msgid "Activate desktop notifications" msgstr "" -#: mod/settings.php:1277 +#: mod/settings.php:1281 msgid "Show desktop popup on new notifications" msgstr "" -#: mod/settings.php:1279 +#: mod/settings.php:1283 msgid "Text-only notification emails" msgstr "" -#: mod/settings.php:1281 +#: mod/settings.php:1285 msgid "Send text only notification emails, without the html part" msgstr "" -#: mod/settings.php:1283 +#: mod/settings.php:1287 msgid "Advanced Account/Page Type Settings" msgstr "" -#: mod/settings.php:1284 +#: mod/settings.php:1288 msgid "Change the behaviour of this account for special situations" msgstr "" -#: mod/settings.php:1287 +#: mod/settings.php:1291 msgid "Relocate" msgstr "" -#: mod/settings.php:1288 +#: mod/settings.php:1292 msgid "" "If you have moved this profile from another server, and some of your " "contacts don't receive your updates, try pushing this button." msgstr "" -#: mod/settings.php:1289 +#: mod/settings.php:1293 msgid "Resend relocate message to contacts" msgstr "" -#: mod/dfrn_request.php:96 +#: mod/dfrn_request.php:98 msgid "This introduction has already been accepted." msgstr "" -#: mod/dfrn_request.php:119 mod/dfrn_request.php:516 +#: mod/dfrn_request.php:121 mod/dfrn_request.php:514 msgid "Profile location is not valid or does not contain profile information." msgstr "" -#: mod/dfrn_request.php:124 mod/dfrn_request.php:521 +#: mod/dfrn_request.php:126 mod/dfrn_request.php:519 msgid "Warning: profile location has no identifiable owner name." msgstr "" -#: mod/dfrn_request.php:126 mod/dfrn_request.php:523 +#: mod/dfrn_request.php:128 mod/dfrn_request.php:521 msgid "Warning: profile location has no profile photo." msgstr "" -#: mod/dfrn_request.php:129 mod/dfrn_request.php:526 +#: mod/dfrn_request.php:131 mod/dfrn_request.php:524 #, php-format msgid "%d required parameter was not found at the given location" msgid_plural "%d required parameters were not found at the given location" msgstr[0] "" msgstr[1] "" -#: mod/dfrn_request.php:172 +#: mod/dfrn_request.php:174 msgid "Introduction complete." msgstr "" @@ -4909,93 +4965,93 @@ msgstr "" msgid "This account has not been configured for email. Request failed." msgstr "" -#: mod/dfrn_request.php:474 +#: mod/dfrn_request.php:472 msgid "You have already introduced yourself here." msgstr "" -#: mod/dfrn_request.php:478 +#: mod/dfrn_request.php:476 #, php-format msgid "Apparently you are already friends with %s." msgstr "" -#: mod/dfrn_request.php:499 +#: mod/dfrn_request.php:497 msgid "Invalid profile URL." msgstr "" -#: mod/dfrn_request.php:505 include/follow.php:72 +#: mod/dfrn_request.php:503 include/follow.php:76 msgid "Disallowed profile URL." msgstr "" -#: mod/dfrn_request.php:596 +#: mod/dfrn_request.php:594 msgid "Your introduction has been sent." msgstr "" -#: mod/dfrn_request.php:636 +#: mod/dfrn_request.php:634 msgid "" "Remote subscription can't be done for your network. Please subscribe " "directly on your system." msgstr "" -#: mod/dfrn_request.php:659 +#: mod/dfrn_request.php:657 msgid "Please login to confirm introduction." msgstr "" -#: mod/dfrn_request.php:669 +#: mod/dfrn_request.php:667 msgid "" "Incorrect identity currently logged in. Please login to <strong>this</" "strong> profile." msgstr "" -#: mod/dfrn_request.php:683 mod/dfrn_request.php:700 +#: mod/dfrn_request.php:681 mod/dfrn_request.php:698 msgid "Confirm" msgstr "" -#: mod/dfrn_request.php:695 +#: mod/dfrn_request.php:693 msgid "Hide this contact" msgstr "" -#: mod/dfrn_request.php:698 +#: mod/dfrn_request.php:696 #, php-format msgid "Welcome home %s." msgstr "" -#: mod/dfrn_request.php:699 +#: mod/dfrn_request.php:697 #, php-format msgid "Please confirm your introduction/connection request to %s." msgstr "" -#: mod/dfrn_request.php:828 +#: mod/dfrn_request.php:826 msgid "" "Please enter your 'Identity Address' from one of the following supported " "communications networks:" msgstr "" -#: mod/dfrn_request.php:849 +#: mod/dfrn_request.php:847 #, php-format msgid "" "If you are not yet a member of the free social web, <a href=\"%s/siteinfo" "\">follow this link to find a public Friendica site and join us today</a>." msgstr "" -#: mod/dfrn_request.php:854 +#: mod/dfrn_request.php:852 msgid "Friend/Connection Request" msgstr "" -#: mod/dfrn_request.php:855 +#: mod/dfrn_request.php:853 msgid "" "Examples: jojo@demo.friendica.com, http://demo.friendica.com/profile/jojo, " "testuser@identi.ca" msgstr "" -#: mod/dfrn_request.php:863 include/contact_selectors.php:76 +#: mod/dfrn_request.php:861 include/contact_selectors.php:76 msgid "Friendica" msgstr "" -#: mod/dfrn_request.php:864 +#: mod/dfrn_request.php:862 msgid "StatusNet/Federated Social Web" msgstr "" -#: mod/dfrn_request.php:866 +#: mod/dfrn_request.php:864 #, php-format msgid "" " - please do not use this form. Instead, enter %s into your Diaspora search " @@ -5083,7 +5139,7 @@ msgstr "" msgid "Choose a nickname: " msgstr "" -#: mod/register.php:280 boot.php:1405 include/nav.php:108 +#: mod/register.php:280 boot.php:1495 include/nav.php:108 msgid "Register" msgstr "" @@ -5111,7 +5167,7 @@ msgstr "" msgid "Only one search per minute is permitted for not logged in users." msgstr "" -#: mod/search.php:136 include/text.php:1003 include/nav.php:118 +#: mod/search.php:136 include/text.php:974 include/nav.php:118 msgid "Search" msgstr "" @@ -5125,16 +5181,16 @@ msgstr "" msgid "Search results for: %s" msgstr "" -#: mod/directory.php:149 include/identity.php:313 include/identity.php:610 +#: mod/directory.php:149 include/identity.php:314 include/identity.php:611 msgid "Status:" msgstr "" -#: mod/directory.php:151 include/identity.php:315 include/identity.php:621 +#: mod/directory.php:151 include/identity.php:316 include/identity.php:622 msgid "Homepage:" msgstr "" #: mod/directory.php:203 view/theme/diabook/theme.php:525 -#: view/theme/vier/theme.php:205 +#: view/theme/vier/theme.php:201 msgid "Global Directory" msgstr "" @@ -5193,21 +5249,21 @@ msgstr "" msgid "No contacts in common." msgstr "" -#: mod/uexport.php:77 +#: mod/uexport.php:29 msgid "Export account" msgstr "" -#: mod/uexport.php:77 +#: mod/uexport.php:29 msgid "" "Export your account info and contacts. Use this to make a backup of your " "account and/or to move it to another server." msgstr "" -#: mod/uexport.php:78 +#: mod/uexport.php:30 msgid "Export all" msgstr "" -#: mod/uexport.php:78 +#: mod/uexport.php:30 msgid "" "Export your accout info, contacts and all your items as json. Could be a " "very big file, and could take a lot of time. Use this to make a full backup " @@ -5242,7 +5298,7 @@ msgid "Ignore/Hide" msgstr "" #: mod/suggest.php:111 include/contact_widgets.php:35 -#: view/theme/diabook/theme.php:527 view/theme/vier/theme.php:207 +#: view/theme/diabook/theme.php:527 view/theme/vier/theme.php:203 msgid "Friend Suggestions" msgstr "" @@ -5274,11 +5330,11 @@ msgstr "" msgid "Romantic Partner" msgstr "" -#: mod/profiles.php:344 mod/photos.php:1647 include/conversation.php:508 +#: mod/profiles.php:344 mod/photos.php:1633 include/conversation.php:508 msgid "Likes" msgstr "" -#: mod/profiles.php:348 mod/photos.php:1647 include/conversation.php:508 +#: mod/profiles.php:348 mod/photos.php:1633 include/conversation.php:508 msgid "Dislikes" msgstr "" @@ -5306,7 +5362,7 @@ msgstr "" msgid "Homepage" msgstr "" -#: mod/profiles.php:375 mod/profiles.php:708 +#: mod/profiles.php:375 mod/profiles.php:686 msgid "Interests" msgstr "" @@ -5314,7 +5370,7 @@ msgstr "" msgid "Address" msgstr "" -#: mod/profiles.php:386 mod/profiles.php:704 +#: mod/profiles.php:386 mod/profiles.php:682 msgid "Location" msgstr "" @@ -5322,260 +5378,260 @@ msgstr "" msgid "Profile updated." msgstr "" -#: mod/profiles.php:565 +#: mod/profiles.php:551 msgid " and " msgstr "" -#: mod/profiles.php:573 +#: mod/profiles.php:559 msgid "public profile" msgstr "" -#: mod/profiles.php:576 +#: mod/profiles.php:562 #, php-format msgid "%1$s changed %2$s to “%3$s”" msgstr "" -#: mod/profiles.php:577 +#: mod/profiles.php:563 #, php-format msgid " - Visit %1$s's %2$s" msgstr "" -#: mod/profiles.php:580 +#: mod/profiles.php:566 #, php-format msgid "%1$s has an updated %2$s, changing %3$s." msgstr "" -#: mod/profiles.php:655 +#: mod/profiles.php:633 msgid "Hide contacts and friends:" msgstr "" -#: mod/profiles.php:660 +#: mod/profiles.php:638 msgid "Hide your contact/friend list from viewers of this profile?" msgstr "" -#: mod/profiles.php:684 +#: mod/profiles.php:662 msgid "Show more profile fields:" msgstr "" -#: mod/profiles.php:695 +#: mod/profiles.php:673 msgid "Edit Profile Details" msgstr "" -#: mod/profiles.php:697 +#: mod/profiles.php:675 msgid "Change Profile Photo" msgstr "" -#: mod/profiles.php:698 +#: mod/profiles.php:676 msgid "View this profile" msgstr "" -#: mod/profiles.php:699 +#: mod/profiles.php:677 msgid "Create a new profile using these settings" msgstr "" -#: mod/profiles.php:700 +#: mod/profiles.php:678 msgid "Clone this profile" msgstr "" -#: mod/profiles.php:701 +#: mod/profiles.php:679 msgid "Delete this profile" msgstr "" -#: mod/profiles.php:702 +#: mod/profiles.php:680 msgid "Basic information" msgstr "" -#: mod/profiles.php:703 +#: mod/profiles.php:681 msgid "Profile picture" msgstr "" -#: mod/profiles.php:705 +#: mod/profiles.php:683 msgid "Preferences" msgstr "" -#: mod/profiles.php:706 +#: mod/profiles.php:684 msgid "Status information" msgstr "" -#: mod/profiles.php:707 +#: mod/profiles.php:685 msgid "Additional information" msgstr "" -#: mod/profiles.php:710 +#: mod/profiles.php:688 msgid "Profile Name:" msgstr "" -#: mod/profiles.php:711 +#: mod/profiles.php:689 msgid "Your Full Name:" msgstr "" -#: mod/profiles.php:712 +#: mod/profiles.php:690 msgid "Title/Description:" msgstr "" -#: mod/profiles.php:713 +#: mod/profiles.php:691 msgid "Your Gender:" msgstr "" -#: mod/profiles.php:714 +#: mod/profiles.php:692 msgid "Birthday :" msgstr "" -#: mod/profiles.php:715 +#: mod/profiles.php:693 msgid "Street Address:" msgstr "" -#: mod/profiles.php:716 +#: mod/profiles.php:694 msgid "Locality/City:" msgstr "" -#: mod/profiles.php:717 +#: mod/profiles.php:695 msgid "Postal/Zip Code:" msgstr "" -#: mod/profiles.php:718 +#: mod/profiles.php:696 msgid "Country:" msgstr "" -#: mod/profiles.php:719 +#: mod/profiles.php:697 msgid "Region/State:" msgstr "" -#: mod/profiles.php:720 +#: mod/profiles.php:698 msgid "<span class=\"heart\">♥</span> Marital Status:" msgstr "" -#: mod/profiles.php:721 +#: mod/profiles.php:699 msgid "Who: (if applicable)" msgstr "" -#: mod/profiles.php:722 +#: mod/profiles.php:700 msgid "Examples: cathy123, Cathy Williams, cathy@example.com" msgstr "" -#: mod/profiles.php:723 +#: mod/profiles.php:701 msgid "Since [date]:" msgstr "" -#: mod/profiles.php:724 include/identity.php:619 +#: mod/profiles.php:702 include/identity.php:620 msgid "Sexual Preference:" msgstr "" -#: mod/profiles.php:725 +#: mod/profiles.php:703 msgid "Homepage URL:" msgstr "" -#: mod/profiles.php:726 include/identity.php:623 +#: mod/profiles.php:704 include/identity.php:624 msgid "Hometown:" msgstr "" -#: mod/profiles.php:727 include/identity.php:627 +#: mod/profiles.php:705 include/identity.php:628 msgid "Political Views:" msgstr "" -#: mod/profiles.php:728 +#: mod/profiles.php:706 msgid "Religious Views:" msgstr "" -#: mod/profiles.php:729 +#: mod/profiles.php:707 msgid "Public Keywords:" msgstr "" -#: mod/profiles.php:730 +#: mod/profiles.php:708 msgid "Private Keywords:" msgstr "" -#: mod/profiles.php:731 include/identity.php:635 +#: mod/profiles.php:709 include/identity.php:636 msgid "Likes:" msgstr "" -#: mod/profiles.php:732 include/identity.php:637 +#: mod/profiles.php:710 include/identity.php:638 msgid "Dislikes:" msgstr "" -#: mod/profiles.php:733 +#: mod/profiles.php:711 msgid "Example: fishing photography software" msgstr "" -#: mod/profiles.php:734 +#: mod/profiles.php:712 msgid "(Used for suggesting potential friends, can be seen by others)" msgstr "" -#: mod/profiles.php:735 +#: mod/profiles.php:713 msgid "(Used for searching profiles, never shown to others)" msgstr "" -#: mod/profiles.php:736 +#: mod/profiles.php:714 msgid "Tell us about yourself..." msgstr "" -#: mod/profiles.php:737 +#: mod/profiles.php:715 msgid "Hobbies/Interests" msgstr "" -#: mod/profiles.php:738 +#: mod/profiles.php:716 msgid "Contact information and Social Networks" msgstr "" -#: mod/profiles.php:739 +#: mod/profiles.php:717 msgid "Musical interests" msgstr "" -#: mod/profiles.php:740 +#: mod/profiles.php:718 msgid "Books, literature" msgstr "" -#: mod/profiles.php:741 +#: mod/profiles.php:719 msgid "Television" msgstr "" -#: mod/profiles.php:742 +#: mod/profiles.php:720 msgid "Film/dance/culture/entertainment" msgstr "" -#: mod/profiles.php:743 +#: mod/profiles.php:721 msgid "Love/romance" msgstr "" -#: mod/profiles.php:744 +#: mod/profiles.php:722 msgid "Work/employment" msgstr "" -#: mod/profiles.php:745 +#: mod/profiles.php:723 msgid "School/education" msgstr "" -#: mod/profiles.php:750 +#: mod/profiles.php:728 msgid "" "This is your <strong>public</strong> profile.<br />It <strong>may</strong> " "be visible to anybody using the internet." msgstr "" -#: mod/profiles.php:760 +#: mod/profiles.php:738 msgid "Age: " msgstr "" -#: mod/profiles.php:813 +#: mod/profiles.php:791 msgid "Edit/Manage Profiles" msgstr "" -#: mod/profiles.php:814 include/identity.php:260 include/identity.php:286 +#: mod/profiles.php:792 include/identity.php:261 include/identity.php:287 msgid "Change profile photo" msgstr "" -#: mod/profiles.php:815 include/identity.php:261 +#: mod/profiles.php:793 include/identity.php:262 msgid "Create New Profile" msgstr "" -#: mod/profiles.php:826 include/identity.php:271 +#: mod/profiles.php:804 include/identity.php:272 msgid "Profile Image" msgstr "" -#: mod/profiles.php:828 include/identity.php:274 +#: mod/profiles.php:806 include/identity.php:275 msgid "visible to everybody" msgstr "" -#: mod/profiles.php:829 include/identity.php:275 +#: mod/profiles.php:807 include/identity.php:276 msgid "Edit visibility" msgstr "" @@ -5721,7 +5777,7 @@ msgstr "" msgid "Visible to:" msgstr "" -#: mod/notes.php:46 include/identity.php:730 +#: mod/notes.php:46 include/identity.php:731 msgid "Personal Notes" msgstr "" @@ -5878,15 +5934,15 @@ msgid "" "important, please visit http://friendica.com" msgstr "" -#: mod/photos.php:99 include/identity.php:705 +#: mod/photos.php:99 include/identity.php:706 msgid "Photo Albums" msgstr "" -#: mod/photos.php:100 mod/photos.php:1899 +#: mod/photos.php:100 mod/photos.php:1885 msgid "Recent Photos" msgstr "" -#: mod/photos.php:103 mod/photos.php:1320 mod/photos.php:1901 +#: mod/photos.php:103 mod/photos.php:1306 mod/photos.php:1887 msgid "Upload New Photos" msgstr "" @@ -5898,7 +5954,7 @@ msgstr "" msgid "Album not found." msgstr "" -#: mod/photos.php:232 mod/photos.php:244 mod/photos.php:1262 +#: mod/photos.php:232 mod/photos.php:244 mod/photos.php:1248 msgid "Delete Album" msgstr "" @@ -5906,7 +5962,7 @@ msgstr "" msgid "Do you really want to delete this photo album and all its photos?" msgstr "" -#: mod/photos.php:322 mod/photos.php:333 mod/photos.php:1580 +#: mod/photos.php:322 mod/photos.php:333 mod/photos.php:1566 msgid "Delete Photo" msgstr "" @@ -5923,151 +5979,151 @@ msgstr "" msgid "a photo" msgstr "" -#: mod/photos.php:819 +#: mod/photos.php:813 msgid "Image file is empty." msgstr "" -#: mod/photos.php:986 +#: mod/photos.php:972 msgid "No photos selected" msgstr "" -#: mod/photos.php:1147 +#: mod/photos.php:1133 #, php-format msgid "You have used %1$.2f Mbytes of %2$.2f Mbytes photo storage." msgstr "" -#: mod/photos.php:1182 +#: mod/photos.php:1168 msgid "Upload Photos" msgstr "" -#: mod/photos.php:1186 mod/photos.php:1257 +#: mod/photos.php:1172 mod/photos.php:1243 msgid "New album name: " msgstr "" -#: mod/photos.php:1187 +#: mod/photos.php:1173 msgid "or existing album name: " msgstr "" -#: mod/photos.php:1188 +#: mod/photos.php:1174 msgid "Do not show a status post for this upload" msgstr "" -#: mod/photos.php:1190 mod/photos.php:1575 include/acl_selectors.php:347 +#: mod/photos.php:1176 mod/photos.php:1561 include/acl_selectors.php:347 msgid "Permissions" msgstr "" -#: mod/photos.php:1201 +#: mod/photos.php:1187 msgid "Private Photo" msgstr "" -#: mod/photos.php:1202 +#: mod/photos.php:1188 msgid "Public Photo" msgstr "" -#: mod/photos.php:1270 +#: mod/photos.php:1256 msgid "Edit Album" msgstr "" -#: mod/photos.php:1276 +#: mod/photos.php:1262 msgid "Show Newest First" msgstr "" -#: mod/photos.php:1278 +#: mod/photos.php:1264 msgid "Show Oldest First" msgstr "" -#: mod/photos.php:1306 mod/photos.php:1884 +#: mod/photos.php:1292 mod/photos.php:1870 msgid "View Photo" msgstr "" -#: mod/photos.php:1353 +#: mod/photos.php:1339 msgid "Permission denied. Access to this item may be restricted." msgstr "" -#: mod/photos.php:1355 +#: mod/photos.php:1341 msgid "Photo not available" msgstr "" -#: mod/photos.php:1411 +#: mod/photos.php:1397 msgid "View photo" msgstr "" -#: mod/photos.php:1411 +#: mod/photos.php:1397 msgid "Edit photo" msgstr "" -#: mod/photos.php:1412 +#: mod/photos.php:1398 msgid "Use as profile photo" msgstr "" -#: mod/photos.php:1437 +#: mod/photos.php:1423 msgid "View Full Size" msgstr "" -#: mod/photos.php:1523 +#: mod/photos.php:1509 msgid "Tags: " msgstr "" -#: mod/photos.php:1526 +#: mod/photos.php:1512 msgid "[Remove any tag]" msgstr "" -#: mod/photos.php:1566 +#: mod/photos.php:1552 msgid "New album name" msgstr "" -#: mod/photos.php:1567 +#: mod/photos.php:1553 msgid "Caption" msgstr "" -#: mod/photos.php:1568 +#: mod/photos.php:1554 msgid "Add a Tag" msgstr "" -#: mod/photos.php:1568 +#: mod/photos.php:1554 msgid "Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping" msgstr "" -#: mod/photos.php:1569 +#: mod/photos.php:1555 msgid "Do not rotate" msgstr "" -#: mod/photos.php:1570 +#: mod/photos.php:1556 msgid "Rotate CW (right)" msgstr "" -#: mod/photos.php:1571 +#: mod/photos.php:1557 msgid "Rotate CCW (left)" msgstr "" -#: mod/photos.php:1586 +#: mod/photos.php:1572 msgid "Private photo" msgstr "" -#: mod/photos.php:1587 +#: mod/photos.php:1573 msgid "Public photo" msgstr "" -#: mod/photos.php:1609 include/conversation.php:1182 +#: mod/photos.php:1595 include/conversation.php:1182 msgid "Share" msgstr "" -#: mod/photos.php:1648 include/conversation.php:509 +#: mod/photos.php:1634 include/conversation.php:509 #: include/conversation.php:1413 msgid "Attending" msgid_plural "Attending" msgstr[0] "" msgstr[1] "" -#: mod/photos.php:1648 include/conversation.php:509 +#: mod/photos.php:1634 include/conversation.php:509 msgid "Not attending" msgstr "" -#: mod/photos.php:1648 include/conversation.php:509 +#: mod/photos.php:1634 include/conversation.php:509 msgid "Might attend" msgstr "" -#: mod/photos.php:1813 +#: mod/photos.php:1799 msgid "Map" msgstr "" @@ -6127,60 +6183,60 @@ msgstr "" msgid "Item was not found." msgstr "" -#: boot.php:868 +#: boot.php:870 msgid "Delete this item?" msgstr "" -#: boot.php:871 +#: boot.php:873 msgid "show fewer" msgstr "" -#: boot.php:1292 +#: boot.php:1382 #, php-format msgid "Update %s failed. See error logs." msgstr "" -#: boot.php:1404 +#: boot.php:1494 msgid "Create a New Account" msgstr "" -#: boot.php:1429 include/nav.php:72 +#: boot.php:1519 include/nav.php:72 msgid "Logout" msgstr "" -#: boot.php:1432 +#: boot.php:1522 msgid "Nickname or Email address: " msgstr "" -#: boot.php:1433 +#: boot.php:1523 msgid "Password: " msgstr "" -#: boot.php:1434 +#: boot.php:1524 msgid "Remember me" msgstr "" -#: boot.php:1437 +#: boot.php:1527 msgid "Or login using OpenID: " msgstr "" -#: boot.php:1443 +#: boot.php:1533 msgid "Forgot your password?" msgstr "" -#: boot.php:1446 +#: boot.php:1536 msgid "Website Terms of Service" msgstr "" -#: boot.php:1447 +#: boot.php:1537 msgid "terms of service" msgstr "" -#: boot.php:1449 +#: boot.php:1539 msgid "Website Privacy Policy" msgstr "" -#: boot.php:1450 +#: boot.php:1540 msgid "privacy policy" msgstr "" @@ -6224,6 +6280,16 @@ msgstr "" msgid "via" msgstr "" +#: include/dfrn.php:1092 +#, php-format +msgid "%s\\'s birthday" +msgstr "" + +#: include/dfrn.php:1093 include/datetime.php:565 +#, php-format +msgid "Happy Birthday %s" +msgstr "" + #: include/dbstructure.php:26 #, php-format msgid "" @@ -6296,7 +6362,7 @@ msgid "Examples: Robert Morgenstein, Fishing" msgstr "" #: include/contact_widgets.php:36 view/theme/diabook/theme.php:526 -#: view/theme/vier/theme.php:206 +#: view/theme/vier/theme.php:202 msgid "Similar Interests" msgstr "" @@ -6305,7 +6371,7 @@ msgid "Random Profile" msgstr "" #: include/contact_widgets.php:38 view/theme/diabook/theme.php:528 -#: view/theme/vier/theme.php:208 +#: view/theme/vier/theme.php:204 msgid "Invite Friends" msgstr "" @@ -6527,58 +6593,58 @@ msgstr "" msgid "Show visitors public community forums at the Advanced Profile Page" msgstr "" -#: include/follow.php:77 +#: include/follow.php:81 msgid "Connect URL missing." msgstr "" -#: include/follow.php:104 +#: include/follow.php:108 msgid "" "This site is not configured to allow communications with other networks." msgstr "" -#: include/follow.php:105 include/follow.php:125 +#: include/follow.php:109 include/follow.php:129 msgid "No compatible communication protocols or feeds were discovered." msgstr "" -#: include/follow.php:123 +#: include/follow.php:127 msgid "The profile address specified does not provide adequate information." msgstr "" -#: include/follow.php:127 +#: include/follow.php:131 msgid "An author or name was not found." msgstr "" -#: include/follow.php:129 +#: include/follow.php:133 msgid "No browser URL could be matched to this address." msgstr "" -#: include/follow.php:131 +#: include/follow.php:135 msgid "" "Unable to match @-style Identity Address with a known protocol or email " "contact." msgstr "" -#: include/follow.php:132 +#: include/follow.php:136 msgid "Use mailto: in front of address to force email check." msgstr "" -#: include/follow.php:138 +#: include/follow.php:142 msgid "" "The profile address specified belongs to a network which has been disabled " "on this site." msgstr "" -#: include/follow.php:148 +#: include/follow.php:152 msgid "" "Limited profile. This person will be unable to receive direct/personal " "notifications from you." msgstr "" -#: include/follow.php:249 +#: include/follow.php:253 msgid "Unable to retrieve contact information." msgstr "" -#: include/follow.php:302 +#: include/follow.php:288 msgid "following" msgstr "" @@ -6593,245 +6659,240 @@ msgstr "" msgid "Default privacy group for new contacts" msgstr "" -#: include/group.php:239 +#: include/group.php:242 msgid "Everybody" msgstr "" -#: include/group.php:262 +#: include/group.php:265 msgid "edit" msgstr "" -#: include/group.php:285 +#: include/group.php:288 msgid "Edit groups" msgstr "" -#: include/group.php:287 +#: include/group.php:290 msgid "Edit group" msgstr "" -#: include/group.php:288 +#: include/group.php:291 msgid "Create a new group" msgstr "" -#: include/group.php:291 +#: include/group.php:294 msgid "Contacts not in any group" msgstr "" -#: include/datetime.php:43 include/datetime.php:45 +#: include/datetime.php:57 include/datetime.php:59 msgid "Miscellaneous" msgstr "" -#: include/datetime.php:141 +#: include/datetime.php:178 msgid "YYYY-MM-DD or MM-DD" msgstr "" -#: include/datetime.php:271 +#: include/datetime.php:327 msgid "never" msgstr "" -#: include/datetime.php:277 +#: include/datetime.php:333 msgid "less than a second ago" msgstr "" -#: include/datetime.php:287 +#: include/datetime.php:343 msgid "year" msgstr "" -#: include/datetime.php:287 +#: include/datetime.php:343 msgid "years" msgstr "" -#: include/datetime.php:288 +#: include/datetime.php:344 msgid "months" msgstr "" -#: include/datetime.php:289 +#: include/datetime.php:345 msgid "weeks" msgstr "" -#: include/datetime.php:290 +#: include/datetime.php:346 msgid "days" msgstr "" -#: include/datetime.php:291 +#: include/datetime.php:347 msgid "hour" msgstr "" -#: include/datetime.php:291 +#: include/datetime.php:347 msgid "hours" msgstr "" -#: include/datetime.php:292 +#: include/datetime.php:348 msgid "minute" msgstr "" -#: include/datetime.php:292 +#: include/datetime.php:348 msgid "minutes" msgstr "" -#: include/datetime.php:293 +#: include/datetime.php:349 msgid "second" msgstr "" -#: include/datetime.php:293 +#: include/datetime.php:349 msgid "seconds" msgstr "" -#: include/datetime.php:302 +#: include/datetime.php:358 #, php-format msgid "%1$d %2$s ago" msgstr "" -#: include/datetime.php:474 include/items.php:2500 +#: include/datetime.php:564 #, php-format msgid "%s's birthday" msgstr "" -#: include/datetime.php:475 include/items.php:2501 -#, php-format -msgid "Happy Birthday %s" -msgstr "" - #: include/identity.php:42 msgid "Requested account is not available." msgstr "" -#: include/identity.php:95 include/identity.php:284 include/identity.php:662 +#: include/identity.php:95 include/identity.php:285 include/identity.php:663 msgid "Edit profile" msgstr "" -#: include/identity.php:244 +#: include/identity.php:245 msgid "Atom feed" msgstr "" -#: include/identity.php:249 +#: include/identity.php:250 msgid "Message" msgstr "" -#: include/identity.php:255 include/nav.php:185 +#: include/identity.php:256 include/nav.php:185 msgid "Profiles" msgstr "" -#: include/identity.php:255 +#: include/identity.php:256 msgid "Manage/edit profiles" msgstr "" -#: include/identity.php:425 include/identity.php:509 +#: include/identity.php:426 include/identity.php:510 msgid "g A l F d" msgstr "" -#: include/identity.php:426 include/identity.php:510 +#: include/identity.php:427 include/identity.php:511 msgid "F d" msgstr "" -#: include/identity.php:471 include/identity.php:556 +#: include/identity.php:472 include/identity.php:557 msgid "[today]" msgstr "" -#: include/identity.php:483 +#: include/identity.php:484 msgid "Birthday Reminders" msgstr "" -#: include/identity.php:484 +#: include/identity.php:485 msgid "Birthdays this week:" msgstr "" -#: include/identity.php:543 +#: include/identity.php:544 msgid "[No description]" msgstr "" -#: include/identity.php:567 +#: include/identity.php:568 msgid "Event Reminders" msgstr "" -#: include/identity.php:568 +#: include/identity.php:569 msgid "Events this week:" msgstr "" -#: include/identity.php:595 +#: include/identity.php:596 msgid "j F, Y" msgstr "" -#: include/identity.php:596 +#: include/identity.php:597 msgid "j F" msgstr "" -#: include/identity.php:603 +#: include/identity.php:604 msgid "Birthday:" msgstr "" -#: include/identity.php:607 +#: include/identity.php:608 msgid "Age:" msgstr "" -#: include/identity.php:616 +#: include/identity.php:617 #, php-format msgid "for %1$d %2$s" msgstr "" -#: include/identity.php:629 +#: include/identity.php:630 msgid "Religion:" msgstr "" -#: include/identity.php:633 +#: include/identity.php:634 msgid "Hobbies/Interests:" msgstr "" -#: include/identity.php:640 +#: include/identity.php:641 msgid "Contact information and Social Networks:" msgstr "" -#: include/identity.php:642 +#: include/identity.php:643 msgid "Musical interests:" msgstr "" -#: include/identity.php:644 +#: include/identity.php:645 msgid "Books, literature:" msgstr "" -#: include/identity.php:646 +#: include/identity.php:647 msgid "Television:" msgstr "" -#: include/identity.php:648 +#: include/identity.php:649 msgid "Film/dance/culture/entertainment:" msgstr "" -#: include/identity.php:650 +#: include/identity.php:651 msgid "Love/Romance:" msgstr "" -#: include/identity.php:652 +#: include/identity.php:653 msgid "Work/employment:" msgstr "" -#: include/identity.php:654 +#: include/identity.php:655 msgid "School/education:" msgstr "" -#: include/identity.php:658 +#: include/identity.php:659 msgid "Forums:" msgstr "" -#: include/identity.php:710 include/identity.php:713 include/nav.php:78 +#: include/identity.php:711 include/identity.php:714 include/nav.php:78 msgid "Videos" msgstr "" -#: include/identity.php:725 include/nav.php:140 +#: include/identity.php:726 include/nav.php:140 msgid "Events and Calendar" msgstr "" -#: include/identity.php:733 +#: include/identity.php:734 msgid "Only You Can See This" msgstr "" #: include/like.php:167 include/conversation.php:122 -#: include/conversation.php:258 include/text.php:1998 +#: include/conversation.php:258 include/text.php:1921 #: view/theme/diabook/theme.php:463 msgid "event" msgstr "" -#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2185 +#: include/like.php:184 include/conversation.php:141 include/diaspora.php:2133 #: view/theme/diabook/theme.php:480 #, php-format msgid "%1$s likes %2$s's %3$s" @@ -6892,31 +6953,31 @@ msgstr "" msgid "stopped following" msgstr "" -#: include/Contact.php:337 include/conversation.php:911 +#: include/Contact.php:339 include/conversation.php:911 msgid "View Status" msgstr "" -#: include/Contact.php:339 include/conversation.php:913 +#: include/Contact.php:341 include/conversation.php:913 msgid "View Photos" msgstr "" -#: include/Contact.php:340 include/conversation.php:914 +#: include/Contact.php:342 include/conversation.php:914 msgid "Network Posts" msgstr "" -#: include/Contact.php:341 include/conversation.php:915 +#: include/Contact.php:343 include/conversation.php:915 msgid "Edit Contact" msgstr "" -#: include/Contact.php:342 +#: include/Contact.php:344 msgid "Drop Contact" msgstr "" -#: include/Contact.php:343 include/conversation.php:916 +#: include/Contact.php:345 include/conversation.php:916 msgid "Send PM" msgstr "" -#: include/Contact.php:344 include/conversation.php:920 +#: include/Contact.php:346 include/conversation.php:920 msgid "Poke" msgstr "" @@ -7131,16 +7192,7 @@ msgid_plural "Undecided" msgstr[0] "" msgstr[1] "" -#: include/forums.php:105 include/text.php:1015 include/nav.php:126 -#: view/theme/vier/theme.php:259 -msgid "Forums" -msgstr "" - -#: include/forums.php:107 view/theme/vier/theme.php:261 -msgid "External link to forum" -msgstr "" - -#: include/network.php:967 +#: include/network.php:975 msgid "view full size" msgstr "" @@ -7176,186 +7228,191 @@ msgstr "" msgid "The end" msgstr "" -#: include/text.php:894 +#: include/text.php:865 msgid "No contacts" msgstr "" -#: include/text.php:909 +#: include/text.php:880 #, php-format msgid "%d Contact" msgid_plural "%d Contacts" msgstr[0] "" msgstr[1] "" -#: include/text.php:921 +#: include/text.php:892 msgid "View Contacts" msgstr "" -#: include/text.php:1010 include/nav.php:121 +#: include/text.php:981 include/nav.php:121 msgid "Full Text" msgstr "" -#: include/text.php:1011 include/nav.php:122 +#: include/text.php:982 include/nav.php:122 msgid "Tags" msgstr "" -#: include/text.php:1066 +#: include/text.php:986 include/ForumManager.php:112 include/nav.php:126 +#: view/theme/vier/theme.php:255 +msgid "Forums" +msgstr "" + +#: include/text.php:1037 msgid "poke" msgstr "" -#: include/text.php:1066 +#: include/text.php:1037 msgid "poked" msgstr "" -#: include/text.php:1067 +#: include/text.php:1038 msgid "ping" msgstr "" -#: include/text.php:1067 +#: include/text.php:1038 msgid "pinged" msgstr "" -#: include/text.php:1068 +#: include/text.php:1039 msgid "prod" msgstr "" -#: include/text.php:1068 +#: include/text.php:1039 msgid "prodded" msgstr "" -#: include/text.php:1069 +#: include/text.php:1040 msgid "slap" msgstr "" -#: include/text.php:1069 +#: include/text.php:1040 msgid "slapped" msgstr "" -#: include/text.php:1070 +#: include/text.php:1041 msgid "finger" msgstr "" -#: include/text.php:1070 +#: include/text.php:1041 msgid "fingered" msgstr "" -#: include/text.php:1071 +#: include/text.php:1042 msgid "rebuff" msgstr "" -#: include/text.php:1071 +#: include/text.php:1042 msgid "rebuffed" msgstr "" -#: include/text.php:1085 +#: include/text.php:1056 msgid "happy" msgstr "" -#: include/text.php:1086 +#: include/text.php:1057 msgid "sad" msgstr "" -#: include/text.php:1087 +#: include/text.php:1058 msgid "mellow" msgstr "" -#: include/text.php:1088 +#: include/text.php:1059 msgid "tired" msgstr "" -#: include/text.php:1089 +#: include/text.php:1060 msgid "perky" msgstr "" -#: include/text.php:1090 +#: include/text.php:1061 msgid "angry" msgstr "" -#: include/text.php:1091 +#: include/text.php:1062 msgid "stupified" msgstr "" -#: include/text.php:1092 +#: include/text.php:1063 msgid "puzzled" msgstr "" -#: include/text.php:1093 +#: include/text.php:1064 msgid "interested" msgstr "" -#: include/text.php:1094 +#: include/text.php:1065 msgid "bitter" msgstr "" -#: include/text.php:1095 +#: include/text.php:1066 msgid "cheerful" msgstr "" -#: include/text.php:1096 +#: include/text.php:1067 msgid "alive" msgstr "" -#: include/text.php:1097 +#: include/text.php:1068 msgid "annoyed" msgstr "" -#: include/text.php:1098 +#: include/text.php:1069 msgid "anxious" msgstr "" -#: include/text.php:1099 +#: include/text.php:1070 msgid "cranky" msgstr "" -#: include/text.php:1100 +#: include/text.php:1071 msgid "disturbed" msgstr "" -#: include/text.php:1101 +#: include/text.php:1072 msgid "frustrated" msgstr "" -#: include/text.php:1102 +#: include/text.php:1073 msgid "motivated" msgstr "" -#: include/text.php:1103 +#: include/text.php:1074 msgid "relaxed" msgstr "" -#: include/text.php:1104 +#: include/text.php:1075 msgid "surprised" msgstr "" -#: include/text.php:1504 +#: include/text.php:1475 msgid "bytes" msgstr "" -#: include/text.php:1536 include/text.php:1548 +#: include/text.php:1507 include/text.php:1519 msgid "Click to open/close" msgstr "" -#: include/text.php:1722 +#: include/text.php:1645 msgid "View on separate page" msgstr "" -#: include/text.php:1723 +#: include/text.php:1646 msgid "view on separate page" msgstr "" -#: include/text.php:2002 +#: include/text.php:1925 msgid "activity" msgstr "" -#: include/text.php:2005 +#: include/text.php:1928 msgid "post" msgstr "" -#: include/text.php:2173 +#: include/text.php:2096 msgid "Item filed" msgstr "" -#: include/bbcode.php:482 include/bbcode.php:1157 include/bbcode.php:1158 +#: include/bbcode.php:482 include/bbcode.php:1159 include/bbcode.php:1160 msgid "Image/photo" msgstr "" @@ -7371,11 +7428,11 @@ msgid "" "\"%s\" target=\"_blank\">post</a>" msgstr "" -#: include/bbcode.php:1117 include/bbcode.php:1137 +#: include/bbcode.php:1119 include/bbcode.php:1139 msgid "$1 wrote:" msgstr "" -#: include/bbcode.php:1166 include/bbcode.php:1167 +#: include/bbcode.php:1168 include/bbcode.php:1169 msgid "Encrypted content" msgstr "" @@ -7469,10 +7526,10 @@ msgid "App.net" msgstr "" #: include/contact_selectors.php:103 -msgid "Redmatrix" +msgid "Hubzilla/Redmatrix" msgstr "" -#: include/Scrape.php:624 +#: include/Scrape.php:623 msgid " on Last.fm" msgstr "" @@ -7496,6 +7553,10 @@ msgstr "" msgid "This action is not available under your subscription plan." msgstr "" +#: include/ForumManager.php:114 view/theme/vier/theme.php:257 +msgid "External link to forum" +msgstr "" + #: include/nav.php:72 msgid "End this session" msgstr "" @@ -7648,17 +7709,17 @@ msgstr "" msgid "Site map" msgstr "" -#: include/api.php:878 +#: include/api.php:906 #, php-format msgid "Daily posting limit of %d posts reached. The post was rejected." msgstr "" -#: include/api.php:897 +#: include/api.php:926 #, php-format msgid "Weekly posting limit of %d posts reached. The post was rejected." msgstr "" -#: include/api.php:916 +#: include/api.php:947 #, php-format msgid "Monthly posting limit of %d posts reached. The post was rejected." msgstr "" @@ -7781,27 +7842,27 @@ msgid "" "\t\tThank you and welcome to %2$s." msgstr "" -#: include/diaspora.php:720 +#: include/diaspora.php:719 msgid "Sharing notification from Diaspora network" msgstr "" -#: include/diaspora.php:2625 +#: include/diaspora.php:2570 msgid "Attachments:" msgstr "" -#: include/delivery.php:533 +#: include/delivery.php:438 msgid "(no subject)" msgstr "" -#: include/delivery.php:544 include/enotify.php:37 +#: include/delivery.php:449 include/enotify.php:37 msgid "noreply" msgstr "" -#: include/items.php:4926 +#: include/items.php:1832 msgid "Do you really want to delete this item?" msgstr "" -#: include/items.php:5201 +#: include/items.php:2107 msgid "Archives" msgstr "" @@ -8361,7 +8422,7 @@ msgstr[1] "" msgid "Done. You can now login with your username and password" msgstr "" -#: index.php:442 +#: index.php:434 msgid "toggle mobile" msgstr "" @@ -8443,7 +8504,7 @@ msgstr "" #: view/theme/diabook/config.php:160 view/theme/diabook/theme.php:391 #: view/theme/diabook/theme.php:626 view/theme/vier/config.php:112 -#: view/theme/vier/theme.php:156 +#: view/theme/vier/theme.php:152 msgid "Community Profiles" msgstr "" @@ -8454,19 +8515,19 @@ msgstr "" #: view/theme/diabook/config.php:162 view/theme/diabook/theme.php:606 #: view/theme/diabook/theme.php:628 view/theme/vier/config.php:114 -#: view/theme/vier/theme.php:377 +#: view/theme/vier/theme.php:373 msgid "Connect Services" msgstr "" #: view/theme/diabook/config.php:163 view/theme/diabook/theme.php:523 #: view/theme/diabook/theme.php:629 view/theme/vier/config.php:115 -#: view/theme/vier/theme.php:203 +#: view/theme/vier/theme.php:199 msgid "Find Friends" msgstr "" #: view/theme/diabook/config.php:164 view/theme/diabook/theme.php:412 #: view/theme/diabook/theme.php:630 view/theme/vier/config.php:116 -#: view/theme/vier/theme.php:185 +#: view/theme/vier/theme.php:181 msgid "Last users" msgstr "" @@ -8488,7 +8549,7 @@ msgstr "" msgid "Your personal photos" msgstr "" -#: view/theme/diabook/theme.php:524 view/theme/vier/theme.php:204 +#: view/theme/diabook/theme.php:524 view/theme/vier/theme.php:200 msgid "Local Directory" msgstr "" @@ -8508,7 +8569,7 @@ msgstr "" msgid "Set style" msgstr "" -#: view/theme/vier/theme.php:295 +#: view/theme/vier/theme.php:291 msgid "Quick Start" msgstr "" From b5851931d135d8466f3cc6835e43b7f36e130c20 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Sat, 12 Mar 2016 08:57:07 +0100 Subject: [PATCH 185/273] Hotkey documentation --- doc/Settings.md | 2 -- doc/de/Settings.md | 3 --- 2 files changed, 5 deletions(-) diff --git a/doc/Settings.md b/doc/Settings.md index 86254cb29e..7d909afa09 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -11,8 +11,6 @@ Hot Keys Friendica traps the following keyboard events: * [Pause] - Pauses "Ajax" update activity. This is the process that provides updates without reloading the page. You may wish to pause it to reduce network usage and/or as a debugging aid for javascript developers. A pause indicator will appear at the lower right hand corner of the page. Hit the [pause] key once again to resume. -* [F8] - Displays a language selector - Birthday Notifications --- diff --git a/doc/de/Settings.md b/doc/de/Settings.md index 988b3657c0..4ad9f39ba5 100644 --- a/doc/de/Settings.md +++ b/doc/de/Settings.md @@ -14,9 +14,6 @@ Friendica erfasst die folgenden Tastaturbefehle: * [Pause] - Pausiert die Update-Aktivität via "Ajax". Das ist ein Prozess, der Updates durchführt, ohne die Seite neu zu laden. Du kannst diesen Prozess pausieren, um deine Netzwerkauslastung zu reduzieren und/oder um es in der Javascript-Programmierung zum Debuggen zu nutzen. Ein Pausenzeichen erscheint unten links im Fenster. Klicke die [Pause]-Taste ein weiteres Mal, um die Pause zu beenden. -* [F8] - Zeigt eine Sprachauswahl an - - **Geburtstagsbenachrichtigung** Geburtstage erscheinen auf deiner Startseite für alle Freunde, die in den nächsten 6 Tagen Geburtstag haben. From 14a2aa552a55c238d4a20d8034bee7604fe2b0e4 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 12 Mar 2016 09:21:13 +0100 Subject: [PATCH 186/273] The name in the notifications has to be double encoded --- mod/ping.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/ping.php b/mod/ping.php index 2eb94576b3..544aa446bb 100644 --- a/mod/ping.php +++ b/mod/ping.php @@ -207,7 +207,7 @@ function ping_init(&$a) { call_hooks('ping_xmlize', $n); $notsxml = '<note id="%d" href="%s" name="%s" url="%s" photo="%s" date="%s" seen="%s" timestamp="%s" >%s</note>'."\n"; return sprintf ( $notsxml, intval($n['id']), - xmlify($n['href']), xmlify($n['name']), xmlify($n['url']), xmlify($n['photo']), + xmlify($n['href']), xmlify(xmlify($n['name'])), xmlify($n['url']), xmlify($n['photo']), xmlify(relative_date($n['date'])), xmlify($n['seen']), xmlify(strtotime($local_time)), xmlify($n['message']) ); From 2a864d889e3e567a8286320966adcc32f9a42bb7 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Sat, 12 Mar 2016 16:25:17 +0100 Subject: [PATCH 187/273] missing include file for the SocNet settings --- mod/settings.php | 1 + 1 file changed, 1 insertion(+) diff --git a/mod/settings.php b/mod/settings.php index 0c3b23a44b..c7659212bf 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -1,5 +1,6 @@ <?php +require_once('include/group.php'); function get_theme_config_file($theme){ $a = get_app(); From 0f65eee69567483d5b55023e53014655c38a5237 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 12 Mar 2016 19:19:20 +0100 Subject: [PATCH 188/273] Everything could work - needs some beautification and documentation --- include/diaspora2.php | 473 +++++++++++++----------------------------- 1 file changed, 142 insertions(+), 331 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 7aa0fc6989..3d12ef5bbf 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -123,53 +123,43 @@ class diaspora { $type = $fields->getName(); switch ($type) { - case "account_deletion": // Done - //return true; + case "account_deletion": return self::receive_account_deletion($importer, $fields); - case "comment": // Done - //return true; + case "comment": return self::receive_comment($importer, $sender, $fields); - case "conversation": // Done - //return true; + case "conversation": return self::receive_conversation($importer, $msg, $fields); - case "like": // Done - //return true; + case "like": return self::receive_like($importer, $sender, $fields); - case "message": // Done - //return true; + case "message": return self::receive_message($importer, $fields); case "participation": // Not implemented return self::receive_participation($importer, $fields); - case "photo": // Not needed + case "photo": // Not implemented return self::receive_photo($importer, $fields); case "poll_participation": // Not implemented return self::receive_poll_participation($importer, $fields); - case "profile": // Done - //return true; + case "profile": return self::receive_profile($importer, $fields); case "request": - //return true; return self::receive_request($importer, $fields); - case "reshare": // Done - //return true; + case "reshare": return self::receive_reshare($importer, $fields); - case "retraction": // Done - //return true; + case "retraction": return self::receive_retraction($importer, $sender, $fields); - case "status_message": // Done - //return true; + case "status_message": return self::receive_status_message($importer, $fields); default: @@ -1364,7 +1354,7 @@ EOT; $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); if($u) - $ret = diaspora_share($u[0], $contact_record); + $ret = self::send_share($u[0], $contact_record); } return true; @@ -1557,8 +1547,10 @@ EOT; case "StatusMessage": return self::item_retraction($importer, $contact, $data);; - case "Person": /// @todo an "unshare" shouldn't remove the contact - contact_remove($contact["id"]); + case "Person": + /// @todo What should we do with an "unshare"? + // Removing the contact isn't correct since we still can read the public items + //contact_remove($contact["id"]); return true; default: @@ -1862,41 +1854,43 @@ EOT; } } - return(($return_code) ? $return_code : (-1)); } - public static function send_share($me,$contact) { - $myaddr = self::get_my_handle($me); - $theiraddr = $contact["addr"]; - $data = array("XML" => array("post" => array("request" => array( - "sender_handle" => $myaddr, - "recipient_handle" => $theiraddr - )))); + private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "") { + + $data = array("XML" => array("post" => array($type => $message))); $msg = xml::from_array($data, $xml); - $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + logger('message: '.$msg, LOGGER_DATA); + logger('send guid '.$guid, LOGGER_DEBUG); - return(self::transmit($owner,$contact,$slap, false)); + $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); +die($slap); + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; } - public static function send_unshare($me,$contact) { - $myaddr = self::get_my_handle($me); + public static function send_share($owner,$contact) { - $data = array("XML" => array("post" => array("retraction" => array( - "post_guid" => $me["guid"], - "diaspora_handle" => $myaddr, - "type" => "Person" - )))); + $message = array("sender_handle" => self::get_my_handle($owner), + "recipient_handle" => $contact["addr"]); - $msg = xml::from_array($data, $xml); + return self::build_and_transmit($owner, $contact, "request", $message); + } - $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + public static function send_unshare($owner,$contact) { - return(self::transmit($owner,$contact,$slap, false)); + $message = array("post_guid" => $owner["guid"], + "diaspora_handle" => self::get_my_handle($owner), + "type" => "Person"); + + return self::build_and_transmit($owner, $contact, "retraction", $message); } private function is_reshare($body) { @@ -1969,27 +1963,6 @@ EOT; public static function send_status($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); - $theiraddr = $contact["addr"]; - - $title = $item["title"]; - $body = $item["body"]; - - // convert to markdown - $body = html_entity_decode(bb2diaspora($body)); - - // Adding the title - if(strlen($title)) - $body = "## ".html_entity_decode($title)."\n\n".$body; - - if ($item["attach"]) { - $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); - if(cnt) { - $body .= "\n".t("Attachments:")."\n"; - foreach($matches as $mtch) - $body .= "[".$mtch[3]."](".$mtch[1].")\n"; - } - } - $public = (($item["private"]) ? "false" : "true"); @@ -2005,8 +1978,27 @@ EOT; "created_at" => $created, "provider_display_name" => $item["app"]); - $data = array("XML" => array("post" => array("reshare" => $message))); + $type = "reshare"; } else { + $title = $item["title"]; + $body = $item["body"]; + + // convert to markdown + $body = html_entity_decode(bb2diaspora($body)); + + // Adding the title + if(strlen($title)) + $body = "## ".html_entity_decode($title)."\n\n".$body; + + if ($item["attach"]) { + $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); + if(cnt) { + $body .= "\n".t("Attachments:")."\n"; + foreach($matches as $mtch) + $body .= "[".$mtch[3]."](".$mtch[1].")\n"; + } + } + $location = array(); if ($item["location"] != "") @@ -2029,126 +2021,87 @@ EOT; if (count($location) == 0) unset($message["location"]); - $data = array("XML" => array("post" => array("status_message" => $message))); + $type = "status_message"; } - $msg = xml::from_array($data, $xml); - - logger("status: ".$owner["username"]." -> ".$contact["name"]." base message: ".$msg, LOGGER_DATA); - logger("send guid ".$item["guid"], LOGGER_DEBUG); - - $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); - - $return_code = self::transmit($owner,$contact,$slap, false); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - private function construct_like($item,$owner,$contact,$public_batch = false, $data = null) { + private function construct_like($item, $owner) { - if (is_array($data)) - $message = $data; - else { - $myaddr = self::get_my_handle($owner); + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", - dbesc($item["thr-parent"])); - if(!$p) - return false; + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"])); + if(!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); - $positive = "true"; + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; - $message = array("positive" => $positive, - "guid" => $item["guid"], - "target_type" => $target_type, - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "diaspora_handle" => $myaddr); - } - - $authorsig = self::get_signature($owner, $message); - - if ($message["author_signature"] == "") - $message["author_signature"] = $authorsig; - else - $message["parent_author_signature"] = $authorsig; - - $data = array("XML" => array("post" => array("like" => $message))); - - return xml::from_array($data, $xml); + return(array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $target_type, + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr)); } - private function construct_comment($item,$owner,$contact,$public_batch = false, $data = null) { + private function construct_comment($item, $owner) { - if (is_array($data)) - $message = $data; - else { - $myaddr = self::get_my_handle($owner); + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($item["parent"]), - intval($item["parent"]) - ); + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) + ); - if (!$p) - return false; + if (!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $text = html_entity_decode(bb2diaspora($item["body"])); + $text = html_entity_decode(bb2diaspora($item["body"])); - $message = array("guid" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => "", - "text" => $text, - "diaspora_handle" => $myaddr); - } - - $authorsig = self::get_signature($owner, $message); - - if ($message["author_signature"] == "") - $message["author_signature"] = $authorsig; - else - $message["parent_author_signature"] = $authorsig; - - $data = array("XML" => array("post" => array("comment" => $message))); - - return xml::from_array($data, $xml); + return(array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => "", + "text" => $text, + "diaspora_handle" => $myaddr)); } public static function send_followup($item,$owner,$contact,$public_batch = false) { - if($item['verb'] === ACTIVITY_LIKE) - $msg = self::construct_like($item, $owner, $contact, $public_batch); - else - $msg = self::construct_comment($item, $owner, $contact, $public_batch); + if($item['verb'] === ACTIVITY_LIKE) { + $message = self::construct_like($item, $owner); + $type = "like"; + } else { + $message = self::construct_comment($item, $owner); + $type = "comment"; + } - if (!$msg) - return $msg; + if (!$message) + return false; - logger("base message: ".$msg, LOGGER_DATA); - logger("send guid ".$item["guid"], LOGGER_DEBUG); + $message["author_signature"] = self::get_signature($owner, $message); - $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); - - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } function send_relay($item, $owner, $contact, $public_batch = false) { - if ($item["deleted"]) + if ($item["deleted"]) { $sql_sign_id = "retract_iid"; - else + $type = "relayable_retraction"; + } elseif ($item['verb'] === ACTIVITY_LIKE) { $sql_sign_id = "iid"; + $type = "like"; + } else { + $sql_sign_id = "iid"; + $type = "comment"; + } // fetch the original signature if the relayable was created by a Diaspora // or DFRN user. @@ -2157,24 +2110,32 @@ EOT; intval($item["id"]) ); - if(count($r)) { - $orig_sign = $r[0]; - $signed_text = $orig_sign['signed_text']; - $authorsig = $orig_sign['signature']; - $handle = $orig_sign['signer']; + if (!$r) + return self::send_followup($item, $owner, $contact, $public_batch); + + $orig_sign = $r[0]; + + // Old way - can be removed for the master branch + if ($orig_sign['signed_text'] AND $orig_sign['signature'] AND $orig_sign['signer']) { // Split the signed text - $signed_parts = explode(";", $signed_text); + $signed_parts = explode(";", $orig_sign['signed_text']); - if ($item['verb'] === ACTIVITY_LIKE) { - $data = array("positive" => $signed_parts[0], + if ($item["deleted"]) + $message = array("parent_author_signature" => "", + "target_guid" => $signed_parts[0], + "target_type" => $signed_parts[1], + "sender_handle" => $orig_sign['signer'], + "target_author_signature" => $orig_sign['signature']); + elseif ($item['verb'] === ACTIVITY_LIKE) + $message = array("positive" => $signed_parts[0], "guid" => $signed_parts[1], "target_type" => $signed_parts[2], "parent_guid" => $signed_parts[3], "parent_author_signature" => "", "author_signature" => $orig_sign['signature'], "diaspora_handle" => $signed_parts[4]); - } else { + else { // Remove the comment guid $guid = array_shift($signed_parts); @@ -2187,154 +2148,26 @@ EOT; // Glue the parts together $text = implode(";", $signed_parts); - $data = array("guid" => $guid, + $message = array("guid" => $guid, "parent_guid" => $parent_guid, "parent_author_signature" => "", "author_signature" => $orig_sign['signature'], "text" => implode(";", $signed_parts), "diaspora_handle" => $handle); } + } else { // New way + $message = json_decode($orig_sign['signed_text']); } - if ($item['deleted']) - ; // Relayed Retraction - elseif($item['verb'] === ACTIVITY_LIKE) - $msg = self::construct_like($item, $owner, $contact, $public_batch, $data); - else - $msg = self::construct_comment($item, $owner, $contact, $public_batch, $data); -die($msg); + if ($item["deleted"]) { + $signed_text = $message["target_guid"].';'.$message["target_type"]; + $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } else + $message["parent_author_signature"] = self::get_signature($owner, $message); - logger('base message: '.$msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = self::build_message($msg,$owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item['guid']); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } -/* - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } - else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; - - $like = false; - $relay_retract = false; - $sql_sign_id = 'iid'; - if( $item['deleted']) { - $relay_retract = true; - - $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - - $sql_sign_id = 'retract_iid'; - $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); - } - elseif($item['verb'] === ACTIVITY_LIKE) { - $like = true; - - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); -// $positive = (($item['deleted']) ? 'false' : 'true'); - $positive = 'true'; - - $tpl = get_markup_template('diaspora_like_relay.tpl'); - } - else { // item is a comment - $tpl = get_markup_template('diaspora_comment_relay.tpl'); - } - - - // fetch the original signature if the relayable was created by a Diaspora - // or DFRN user. Relayables for other networks are not supported. - - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1", - intval($item['id']) - ); - if(count($r)) { - $orig_sign = $r[0]; - $signed_text = $orig_sign['signed_text']; - $authorsig = $orig_sign['signature']; - $handle = $orig_sign['signer']; - - // Split the signed text - $signed_parts = explode(";", $signed_text); - - // Remove the parent guid - array_shift($signed_parts); - - // Remove the comment guid - array_shift($signed_parts); - - // Remove the handle - array_pop($signed_parts); - - // Glue the parts together - $text = implode(";", $signed_parts); - } - else { - // This part is meant for cases where we don't have the signatur. (Which shouldn't happen with posts from Diaspora and Friendica) - // This means that the comment won't be accepted by newer Diaspora servers - - $body = $item['body']; - $text = html_entity_decode(bb2diaspora($body)); - - $handle = diaspora_handle_from_contact($item['contact-id']); - if(! $handle) - return; - - if($relay_retract) - $signed_text = $item['guid'] . ';' . $target_type; - elseif($like) - $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle; - else - $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle; - - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - } - - // Sign the relayable with the top-level owner's signature - $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$parentsig' => xmlify($parentauthorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($handle) - )); - - logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)); - - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); -*/ - public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); @@ -2355,21 +2188,10 @@ die($msg); "sender_handle" => $myaddr, "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); - $data = array("XML" => array("post" => array($msg_type => $message))); - $msg = xml::from_array($data, $xml); - - logger("send guid ".$item["guid"], LOGGER_DEBUG); - - $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); - - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); } - public static function send_mail($item,$owner,$contact) { + public static function send_mail($item, $owner, $contact) { $myaddr = self::get_my_handle($owner); @@ -2396,7 +2218,6 @@ die($msg); $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; - $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); $msg = array( @@ -2410,9 +2231,10 @@ die($msg); "conversation_guid" => $cnv["guid"] ); - if ($item["reply"]) - $data = array("XML" => array("post" => array("message" => $msg))); - else { + if ($item["reply"]) { + $message = $msg; + $type = "message"; + } else { $message = array("guid" => $cnv["guid"], "subject" => $cnv["subject"], "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), @@ -2420,21 +2242,10 @@ die($msg); "diaspora_handle" => $cnv["creator"], "participant_handles" => $cnv["recips"]); - $data = array("XML" => array("post" => array("conversation" => $message))); + $type = "conversation"; } - $xmsg = xml::from_array($data, $xml); - - logger("conversation: ".print_r($xmsg,true), LOGGER_DATA); - logger("send guid ".$item["guid"], LOGGER_DEBUG); - - $slap = self::build_message($xmsg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], false); - - $return_code = self::transmit($owner, $contact, $slap, false, false, $item["guid"]); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); } } ?> From f978bc9cc8011ab31e9abd90519008c6248cdebb Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 13 Mar 2016 07:10:24 +0100 Subject: [PATCH 189/273] Some code cleaning, changes to the xml generation --- include/diaspora2.php | 183 ++++++++++++++++++++++++------------------ include/xml.php | 10 ++- 2 files changed, 114 insertions(+), 79 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 3d12ef5bbf..97d5ecee9f 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -20,7 +20,7 @@ require_once("include/datetime.php"); */ class diaspora { - public static function fetch_relay() { + public static function relay_list() { $serverdata = get_config("system", "relay_server"); if ($serverdata == "") @@ -277,13 +277,13 @@ class diaspora { return false; if (isset($parent_author_signature)) { - $key = self::get_key($msg["author"]); + $key = self::key($msg["author"]); if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) return false; } - $key = self::get_key($fields->author); + $key = self::key($fields->author); return rsa_verify($signed_data, $author_signature, $key, "sha256"); } @@ -295,10 +295,10 @@ class diaspora { * * @return string The public key */ - private function get_key($handle) { + private function key($handle) { logger("Fetching diaspora key for: ".$handle); - $r = self::get_person_by_handle($handle); + $r = self::person_by_handle($handle); if($r) return $r["pubkey"]; @@ -312,7 +312,7 @@ class diaspora { * * @return array the queried data */ - private function get_person_by_handle($handle) { + private function person_by_handle($handle) { $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", dbesc(NETWORK_DIASPORA), @@ -407,7 +407,33 @@ class diaspora { return $r; } - private function get_contact_by_handle($uid, $handle) { + public static function handle_from_contact($contact_id) { + $handle = False; + + logger("contact id is ".$contact_id, LOGGER_DEBUG); + + $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", + intval($contact_id) + ); + if($r) { + $contact = $r[0]; + + logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); + + if($contact['addr'] != "") + $handle = $contact['addr']; + elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { + $baseurl_start = strpos($contact['url'],'://') + 3; + $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle + $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); + $handle = $contact['nick'].'@'.$baseurl; + } + } + + return $handle; + } + + private function contact_by_handle($uid, $handle) { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", intval($uid), dbesc($handle) @@ -459,8 +485,8 @@ class diaspora { return false; } - private function get_allowed_contact_by_handle($importer, $handle, $is_comment = false) { - $contact = self::get_contact_by_handle($importer["uid"], $handle); + private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { + $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; @@ -505,7 +531,7 @@ class diaspora { logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); - $msg = self::fetch_message($guid, $server); + $msg = self::message($guid, $server); if (!$msg) return false; @@ -516,7 +542,7 @@ class diaspora { return self::dispatch_public($msg); } - private function fetch_message($guid, $server, $level = 0) { + private function message($guid, $server, $level = 0) { if ($level > 5) return false; @@ -534,10 +560,10 @@ class diaspora { if ($source_xml->post->reshare) { // Reshare of a reshare - old Diaspora version - return self::fetch_message($source_xml->post->reshare->root_guid, $server, ++$level); + return self::message($source_xml->post->reshare->root_guid, $server, ++$level); } elseif ($source_xml->getName() == "reshare") { // Reshare of a reshare - new Diaspora version - return self::fetch_message($source_xml->root_guid, $server, ++$level); + return self::message($source_xml->root_guid, $server, ++$level); } $author = ""; @@ -554,12 +580,12 @@ class diaspora { $msg = array("message" => $x, "author" => $author); - $msg["key"] = self::get_key($msg["author"]); + $msg["key"] = self::key($msg["author"]); return $msg; } - private function fetch_parent_item($uid, $guid, $author, $contact) { + private function parent_item($uid, $guid, $author, $contact) { $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, `author-name`, `author-link`, `author-avatar`, `owner-name`, `owner-link`, `owner-avatar` @@ -570,7 +596,7 @@ class diaspora { $result = self::store_by_guid($guid, $contact["url"], $uid); if (!$result) { - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); $result = self::store_by_guid($guid, $person["url"], $uid); } @@ -592,7 +618,7 @@ class diaspora { return $r[0]; } - private function get_author_contact_by_url($contact, $person, $uid) { + private function author_contact_by_url($contact, $person, $uid) { $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", dbesc(normalise_link($person["url"])), intval($uid)); @@ -636,7 +662,7 @@ class diaspora { private function receive_account_deletion($importer, $data) { $author = notags(unxmlify($data->author)); - $contact = self::get_contact_by_handle($importer["uid"], $author); + $contact = self::contact_by_handle($importer["uid"], $author); if (!$contact) { logger("cannot find contact for author: ".$author); return false; @@ -653,25 +679,25 @@ class diaspora { $text = unxmlify($data->text); $author = notags(unxmlify($data->author)); - $contact = self::get_allowed_contact_by_handle($importer, $sender, true); + $contact = self::allowed_contact_by_handle($importer, $sender, true); if (!$contact) return false; if (self::message_exists($importer["uid"], $guid)) return false; - $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) return false; - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!is_array($person)) { logger("unable to find author details"); return false; } // Fetch the contact id - if we know this contact - $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]); + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); $datarray = array(); @@ -763,7 +789,7 @@ class diaspora { $person = $contact; $key = $msg["key"]; } else { - $person = self::get_person_by_handle($msg_author); + $person = self::person_by_handle($msg_author); if (is_array($person) && x($person, "pubkey")) $key = $person["pubkey"]; @@ -852,7 +878,7 @@ class diaspora { return false; } - $contact = self::get_allowed_contact_by_handle($importer, $msg["author"], true); + $contact = self::allowed_contact_by_handle($importer, $msg["author"], true); if (!$contact) return false; @@ -907,22 +933,17 @@ class diaspora { private function construct_like_object($importer, $parent_item) { $objtype = ACTIVITY_OBJ_NOTE; - $link = xmlify('<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'."\n") ; + $link = '<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'; $parent_body = $parent_item["body"]; - $obj = <<< EOT + $xmldata = array("object" => array("type" => $objtype, + "local" => "1", + "id" => $parent_item["uri"], + "link" => $link, + "title" => "", + "content" => $parent_body)); - <object> - <type>$objtype</type> - <local>1</local> - <id>{$parent_item["uri"]}</id> - <link>$link</link> - <title></title> - <content>$parent_body</content> - </object> -EOT; - - return $obj; + return xml::from_array($xmldata, $xml, true); } private function receive_like($importer, $sender, $data) { @@ -937,25 +958,25 @@ EOT; if (!in_array($parent_type, array("Post", "Comment"))) return false; - $contact = self::get_allowed_contact_by_handle($importer, $sender, true); + $contact = self::allowed_contact_by_handle($importer, $sender, true); if (!$contact) return false; if (self::message_exists($importer["uid"], $guid)) return false; - $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) return false; - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!is_array($person)) { logger("unable to find author details"); return false; } // Fetch the contact id - if we know this contact - $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]); + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora // We would accept this anyhow. @@ -1019,7 +1040,7 @@ EOT; $author = notags(unxmlify($data->author)); $conversation_guid = notags(unxmlify($data->conversation_guid)); - $contact = self::get_allowed_contact_by_handle($importer, $author, true); + $contact = self::allowed_contact_by_handle($importer, $author, true); if (!$contact) return false; @@ -1041,7 +1062,7 @@ EOT; $body = diaspora2bb($text); $message_uri = $author.":".$guid; - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!$person) { logger("unable to find author details"); return false; @@ -1100,7 +1121,7 @@ EOT; private function receive_profile($importer, $data) { $author = notags(unxmlify($data->author)); - $contact = self::get_contact_by_handle($importer["uid"], $author); + $contact = self::contact_by_handle($importer["uid"], $author); if (!$contact) return; @@ -1254,7 +1275,7 @@ EOT; if (!$author || !$recipient) return; - $contact = self::get_contact_by_handle($importer["uid"],$author); + $contact = self::contact_by_handle($importer["uid"],$author); if($contact) { @@ -1265,7 +1286,7 @@ EOT; return true; } - $ret = self::get_person_by_handle($author); + $ret = self::person_by_handle($author); if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { logger("Cannot resolve diaspora handle ".$author ." for ".$recipient); @@ -1295,7 +1316,7 @@ EOT; // find the contact record we just created - $contact_record = self::get_contact_by_handle($importer["uid"],$author); + $contact_record = self::contact_by_handle($importer["uid"],$author); if (!$contact_record) { logger("unable to locate newly created contact record."); @@ -1360,7 +1381,7 @@ EOT; return true; } - private function get_original_item($guid, $orig_author, $author) { + private function original_item($guid, $orig_author, $author) { // Do we already have this item? $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, @@ -1424,14 +1445,14 @@ EOT; $public = notags(unxmlify($data->public)); $created_at = notags(unxmlify($data->created_at)); - $contact = self::get_allowed_contact_by_handle($importer, $author, false); + $contact = self::allowed_contact_by_handle($importer, $author, false); if (!$contact) return false; if (self::message_exists($importer["uid"], $guid)) return false; - $original_item = self::get_original_item($root_guid, $root_author, $author); + $original_item = self::original_item($root_guid, $root_author, $author); if (!$original_item) return false; @@ -1482,7 +1503,7 @@ EOT; $target_guid = notags(unxmlify($data->target_guid)); $author = notags(unxmlify($data->author)); - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!is_array($person)) { logger("unable to find author detail for ".$author); return false; @@ -1533,7 +1554,7 @@ EOT; private function receive_retraction($importer, $sender, $data) { $target_type = notags(unxmlify($data->target_type)); - $contact = self::get_contact_by_handle($importer["uid"], $sender); + $contact = self::contact_by_handle($importer["uid"], $sender); if (!$contact) { logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]); return false; @@ -1575,7 +1596,7 @@ EOT; // print_r($poll); // die("poll!\n"); //} - $contact = self::get_allowed_contact_by_handle($importer, $author, false); + $contact = self::allowed_contact_by_handle($importer, $author, false); if (!$contact) return false; @@ -1652,7 +1673,7 @@ EOT; * Here come all the functions that are needed to transmit data with the Diaspora protocol * *******************************************************************************************/ - private function get_my_handle($me) { + private function my_handle($me) { if ($contact["addr"] != "") return $contact["addr"]; @@ -1665,7 +1686,7 @@ EOT; logger("Message: ".$msg, LOGGER_DATA); - $handle = self::get_my_handle($user); + $handle = self::my_handle($user); $b64url_data = base64url_encode($msg); @@ -1694,7 +1715,7 @@ $magic_env = <<< EOT </me:env> </diaspora> EOT; - +die($magic_env."\n"); logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; } @@ -1720,7 +1741,7 @@ EOT; $outer_iv = random_string(16); $b_outer_iv = base64_encode($outer_iv); - $handle = self::get_my_handle($user); + $handle = self::my_handle($user); $padded_data = pkcs5_pad($msg,16); $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); @@ -1740,14 +1761,11 @@ EOT; $signature = rsa_sign($signable_data,$prvkey); $sig = base64url_encode($signature); -$decrypted_header = <<< EOT -<decrypted_header> - <iv>$b_inner_iv</iv> - <aes_key>$b_inner_aes_key</aes_key> - <author_id>$handle</author_id> -</decrypted_header> -EOT; + $xmldata = array("decrypted_header" => array("iv" => $b_inner_iv, + "aes_key" => $b_inner_aes_key, + "author_id" => $handle)); + $decrypted_header = xml::from_array($xmldata, $xml, true); $decrypted_header = pkcs5_pad($decrypted_header,16); $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); @@ -1765,6 +1783,15 @@ EOT; "ciphertext" => base64_encode($ciphertext))); $cipher_json = base64_encode($encrypted_header_json_object); + $xml = nul; + $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "me:sig" => $sig))); + $encrypted_header = xml::from_array($xmldata, $xml, true); +echo $encrypted_header."\n"; + $encrypted_header = "<encrypted_header>".$cipher_json."</encrypted_header>"; $magic_env = <<< EOT @@ -1779,6 +1806,7 @@ $magic_env = <<< EOT </me:env> </diaspora> EOT; +die($magic_env."\n"); logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; @@ -1796,7 +1824,7 @@ EOT; return $slap; } - private function get_signature($owner, $message) { + private function signature($owner, $message) { $sigmsg = $message; unset($sigmsg["author_signature"]); unset($sigmsg["parent_author_signature"]); @@ -1806,7 +1834,7 @@ EOT; return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); } - private function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { + public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { $a = get_app(); @@ -1878,7 +1906,7 @@ die($slap); public static function send_share($owner,$contact) { - $message = array("sender_handle" => self::get_my_handle($owner), + $message = array("sender_handle" => self::my_handle($owner), "recipient_handle" => $contact["addr"]); return self::build_and_transmit($owner, $contact, "request", $message); @@ -1887,7 +1915,7 @@ die($slap); public static function send_unshare($owner,$contact) { $message = array("post_guid" => $owner["guid"], - "diaspora_handle" => self::get_my_handle($owner), + "diaspora_handle" => self::my_handle($owner), "type" => "Person"); return self::build_and_transmit($owner, $contact, "retraction", $message); @@ -1924,7 +1952,7 @@ die($slap); dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); if ($r) { $ret= array(); - $ret["root_handle"] = diaspora_handle_from_contact($r[0]["contact-id"]); + $ret["root_handle"] = self::handle_from_contact($r[0]["contact-id"]); $ret["root_guid"] = $guid; return($ret); } @@ -1962,7 +1990,7 @@ die($slap); public static function send_status($item, $owner, $contact, $public_batch = false) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $public = (($item["private"]) ? "false" : "true"); @@ -2029,7 +2057,7 @@ die($slap); private function construct_like($item, $owner) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", dbesc($item["thr-parent"])); @@ -2051,7 +2079,7 @@ die($slap); private function construct_comment($item, $owner) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", intval($item["parent"]), @@ -2085,7 +2113,7 @@ die($slap); if (!$message) return false; - $message["author_signature"] = self::get_signature($owner, $message); + $message["author_signature"] = self::signature($owner, $message); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } @@ -2115,7 +2143,8 @@ die($slap); $orig_sign = $r[0]; - // Old way - can be removed for the master branch + // Old way - is used by the internal Friendica functions + /// @todo Change all signatur storing functions to the new format if ($orig_sign['signed_text'] AND $orig_sign['signature'] AND $orig_sign['signer']) { // Split the signed text @@ -2163,14 +2192,14 @@ die($slap); $signed_text = $message["target_guid"].';'.$message["target_type"]; $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); } else - $message["parent_author_signature"] = self::get_signature($owner, $message); + $message["parent_author_signature"] = self::signature($owner, $message); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } public static function send_retraction($item, $owner, $contact, $public_batch = false) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable if ($item["uri"] !== $item["parent-uri"]) { @@ -2193,7 +2222,7 @@ die($slap); public static function send_mail($item, $owner, $contact) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($item["convid"]), diff --git a/include/xml.php b/include/xml.php index 9c458dab12..91480dc03b 100644 --- a/include/xml.php +++ b/include/xml.php @@ -4,7 +4,7 @@ * */ class xml { - function from_array($array, &$xml) { + function from_array($array, &$xml, $remove_header = false) { if (!is_object($xml)) { foreach($array as $key => $value) { @@ -14,7 +14,13 @@ class xml { $dom = dom_import_simplexml($root)->ownerDocument; $dom->formatOutput = true; $xml = $dom; - return $dom->saveXML(); + + $xml_text = $dom->saveXML(); + + if ($remove_header) + $xml_text = trim(substr($xml_text, 21)); + + return $xml_text; } } From f8f19038bf43aef56aae3898324a3658e2f04c14 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 13 Mar 2016 09:57:44 +0100 Subject: [PATCH 190/273] XML generation is now improved --- include/diaspora2.php | 158 +++++++++++++++++++----------------------- include/xml.php | 42 +++++++++-- 2 files changed, 105 insertions(+), 95 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 97d5ecee9f..da772d68bd 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -729,7 +729,6 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - // print_r($datarray); // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1013,7 +1012,6 @@ class diaspora { $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); $message_id = item_store($datarray); - // print_r($datarray); // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1493,7 +1491,6 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - // print_r($datarray); return $message_id; } @@ -1662,16 +1659,15 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - // print_r($datarray); logger("Stored item with message id ".$message_id, LOGGER_DEBUG); return $message_id; } - /******************************************************************************************* - * Here come all the functions that are needed to transmit data with the Diaspora protocol * - *******************************************************************************************/ + /****************************************************************************************** + * Here are all the functions that are needed to transmit data with the Diaspora protocol * + ******************************************************************************************/ private function my_handle($me) { if ($contact["addr"] != "") @@ -1701,21 +1697,18 @@ class diaspora { $signature = rsa_sign($signable_data,$prvkey); $sig = base64url_encode($signature); -$magic_env = <<< EOT -<?xml version='1.0' encoding='UTF-8'?> -<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" > - <header> - <author_id>$handle</author_id> - </header> - <me:env> - <me:encoding>base64url</me:encoding> - <me:alg>RSA-SHA256</me:alg> - <me:data type="application/xml">$data</me:data> - <me:sig>$sig</me:sig> - </me:env> -</diaspora> -EOT; -die($magic_env."\n"); + $xmldata = array("diaspora" => array("header" => array("author_id" => $handle), + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "@attributes" => array("type" => "application/xml"), + "me:sig" => $sig))); + + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); + + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); + logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; } @@ -1783,30 +1776,17 @@ die($magic_env."\n"); "ciphertext" => base64_encode($ciphertext))); $cipher_json = base64_encode($encrypted_header_json_object); - $xml = nul; $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, "me:env" => array("me:encoding" => "base64url", "me:alg" => "RSA-SHA256", "me:data" => $data, + "@attributes" => array("type" => "application/xml"), "me:sig" => $sig))); - $encrypted_header = xml::from_array($xmldata, $xml, true); -echo $encrypted_header."\n"; - $encrypted_header = "<encrypted_header>".$cipher_json."</encrypted_header>"; + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); -$magic_env = <<< EOT -<?xml version='1.0' encoding='UTF-8'?> -<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" > - $encrypted_header - <me:env> - <me:encoding>base64url</me:encoding> - <me:alg>RSA-SHA256</me:alg> - <me:data type="application/xml">$data</me:data> - <me:sig>$sig</me:sig> - </me:env> -</diaspora> -EOT; -die($magic_env."\n"); + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; @@ -1896,7 +1876,7 @@ die($magic_env."\n"); logger('send guid '.$guid, LOGGER_DEBUG); $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); -die($slap); + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); @@ -2118,7 +2098,49 @@ die($slap); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - function send_relay($item, $owner, $contact, $public_batch = false) { + private function message_from_signatur($item, $signature) { + + // Split the signed text + $signed_parts = explode(";", $signature['signed_text']); + + if ($item["deleted"]) + $message = array("parent_author_signature" => "", + "target_guid" => $signed_parts[0], + "target_type" => $signed_parts[1], + "sender_handle" => $signature['signer'], + "target_author_signature" => $signature['signature']); + elseif ($item['verb'] === ACTIVITY_LIKE) + $message = array("positive" => $signed_parts[0], + "guid" => $signed_parts[1], + "target_type" => $signed_parts[2], + "parent_guid" => $signed_parts[3], + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "diaspora_handle" => $signed_parts[4]); + else { + // Remove the comment guid + $guid = array_shift($signed_parts); + + // Remove the parent guid + $parent_guid = array_shift($signed_parts); + + // Remove the handle + $handle = array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); + + $message = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } + return $message; + } + + public static function send_relay($item, $owner, $contact, $public_batch = false) { if ($item["deleted"]) { $sql_sign_id = "retract_iid"; @@ -2131,62 +2153,22 @@ die($slap); $type = "comment"; } - // fetch the original signature if the relayable was created by a Diaspora - // or DFRN user. + // fetch the original signature $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", - intval($item["id"]) - ); + intval($item["id"])); if (!$r) return self::send_followup($item, $owner, $contact, $public_batch); - $orig_sign = $r[0]; + $signature = $r[0]; // Old way - is used by the internal Friendica functions /// @todo Change all signatur storing functions to the new format - if ($orig_sign['signed_text'] AND $orig_sign['signature'] AND $orig_sign['signer']) { - - // Split the signed text - $signed_parts = explode(";", $orig_sign['signed_text']); - - if ($item["deleted"]) - $message = array("parent_author_signature" => "", - "target_guid" => $signed_parts[0], - "target_type" => $signed_parts[1], - "sender_handle" => $orig_sign['signer'], - "target_author_signature" => $orig_sign['signature']); - elseif ($item['verb'] === ACTIVITY_LIKE) - $message = array("positive" => $signed_parts[0], - "guid" => $signed_parts[1], - "target_type" => $signed_parts[2], - "parent_guid" => $signed_parts[3], - "parent_author_signature" => "", - "author_signature" => $orig_sign['signature'], - "diaspora_handle" => $signed_parts[4]); - else { - // Remove the comment guid - $guid = array_shift($signed_parts); - - // Remove the parent guid - $parent_guid = array_shift($signed_parts); - - // Remove the handle - $handle = array_pop($signed_parts); - - // Glue the parts together - $text = implode(";", $signed_parts); - - $message = array("guid" => $guid, - "parent_guid" => $parent_guid, - "parent_author_signature" => "", - "author_signature" => $orig_sign['signature'], - "text" => implode(";", $signed_parts), - "diaspora_handle" => $handle); - } - } else { // New way - $message = json_decode($orig_sign['signed_text']); - } + if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) + $message = self::message_from_signatur($item, $signature); + else // New way + $message = json_decode($signature['signed_text']); if ($item["deleted"]) { $signed_text = $message["target_guid"].';'.$message["target_type"]; diff --git a/include/xml.php b/include/xml.php index 91480dc03b..c2313648ce 100644 --- a/include/xml.php +++ b/include/xml.php @@ -4,12 +4,15 @@ * */ class xml { - function from_array($array, &$xml, $remove_header = false) { + function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) { - if (!is_object($xml)) { + if ($root) { foreach($array as $key => $value) { + foreach ($namespaces AS $nskey => $nsvalue) + $key .= " xmlns".($nskey == "" ? "":":").$nskey.'="'.$nsvalue.'"'; + $root = new SimpleXMLElement("<".$key."/>"); - self::from_array($value, $root); + self::from_array($value, $root, $remove_header, $namespaces, false); $dom = dom_import_simplexml($root)->ownerDocument; $dom->formatOutput = true; @@ -25,10 +28,35 @@ class xml { } foreach($array as $key => $value) { - if (!is_array($value) AND !is_numeric($key)) - $xml->addChild($key, xmlify($value)); - elseif (is_array($value)) - self::from_array($value, $xml->addChild($key)); + if ($key == "@attributes") { + if (!isset($element) OR !is_array($value)) + continue; + + foreach ($value as $attr_key => $attr_value) { + $element_parts = explode(":", $attr_key); + if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]])) + $namespace = $namespaces[$element_parts[0]]; + else + $namespace = NULL; + + $element->addAttribute ($attr_key, $attr_value, $namespace); + } + + continue; + } + + $element_parts = explode(":", $key); + if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]])) + $namespace = $namespaces[$element_parts[0]]; + else + $namespace = NULL; + + if (!is_array($value)) + $element = $xml->addChild($key, xmlify($value), $namespace); + elseif (is_array($value)) { + $element = $xml->addChild($key, NULL, $namespace); + self::from_array($value, $element, $remove_header, $namespaces, false); + } } } From 99d5f8afc6df764679312a97fb2426b40ea20566 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 13 Mar 2016 13:04:12 +0100 Subject: [PATCH 191/273] The display contained bad sql queries --- mod/display.php | 115 ++++++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/mod/display.php b/mod/display.php index 97261e267d..e53f9e2066 100644 --- a/mod/display.php +++ b/mod/display.php @@ -17,7 +17,7 @@ function display_init(&$a) { // Does the local user have this item? if (local_user()) { $r = q("SELECT `id`, `parent`, `author-name`, `author-link`, `author-avatar`, `network`, `body`, `uid` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `guid` = '%s' AND `uid` = %d", dbesc($a->argv[1]), local_user()); if (count($r)) { $nick = $a->user["nickname"]; @@ -30,12 +30,12 @@ function display_init(&$a) { $r = q("SELECT `user`.`nickname`, `item`.`id`, `item`.`parent`, `item`.`author-name`, `item`.`author-link`, `item`.`author-avatar`, `item`.`network`, `item`.`uid`, `item`.`body` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND NOT `user`.`hidewall` + AND NOT `item`.`private` AND NOT `user`.`hidewall` AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` if (count($r)) { $nick = $r[0]["nickname"]; $itemuid = $r[0]["uid"]; @@ -46,17 +46,17 @@ function display_init(&$a) { if ($nick == "") { $r = q("SELECT `item`.`id`, `item`.`parent`, `item`.`author-name`, `item`.`author-link`, `item`.`author-avatar`, `item`.`network`, `item`.`uid`, `item`.`body` - FROM `item` WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + FROM `item` WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND `item`.`uid` = 0 + AND NOT `item`.`private` AND `item`.`uid` = 0 AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` } if (count($r)) { if ($r[0]["id"] != $r[0]["parent"]) $r = q("SELECT `id`, `author-name`, `author-link`, `author-avatar`, `network`, `body`, `uid` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `id` = %d", $r[0]["parent"]); $profiledata = display_fetchauthor($a, $r[0]); @@ -67,7 +67,7 @@ function display_init(&$a) { if (($nickname != $a->user["nickname"])) { $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `contact`.`avatar-date` AS picdate, `user`.* FROM `profile` INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` - WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 and `contact`.`self` = 1 LIMIT 1", + WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", dbesc($nickname) ); if (count($r)) @@ -120,27 +120,27 @@ function display_fetchauthor($a, $item) { } if (!$skip) { - $author = ""; - preg_match("/author='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") + $author = ""; + preg_match("/author='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") $profiledata["name"] = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); - preg_match('/author="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") + preg_match('/author="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") $profiledata["name"] = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") $profiledata["url"] = $matches[1]; - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") $profiledata["url"] = $matches[1]; - $avatar = ""; - preg_match("/avatar='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") + $avatar = ""; + preg_match("/avatar='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") $profiledata["photo"] = $matches[1]; preg_match('/avatar="(.*?)"/ism', $attributes, $matches); @@ -257,7 +257,7 @@ function display_content(&$a, $update = 0) { if (local_user()) { $r = q("SELECT `id` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `guid` = '%s' AND `uid` = %d", dbesc($a->argv[1]), local_user()); if (count($r)) { $item_id = $r[0]["id"]; @@ -267,12 +267,12 @@ function display_content(&$a, $update = 0) { if ($nick == "") { $r = q("SELECT `user`.`nickname`, `item`.`id` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND NOT `user`.`hidewall` + AND NOT `item`.`private` AND NOT `user`.`hidewall` AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` if (count($r)) { $item_id = $r[0]["id"]; $nick = $r[0]["nickname"]; @@ -280,12 +280,12 @@ function display_content(&$a, $update = 0) { } if ($nick == "") { $r = q("SELECT `item`.`id` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND `item`.`uid` = 0 + AND NOT `item`.`private` AND `item`.`uid` = 0 AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` if (count($r)) { $item_id = $r[0]["id"]; } @@ -293,12 +293,22 @@ function display_content(&$a, $update = 0) { } } - if(! $item_id) { + if ($item_id AND !is_numeric($item_id)) { + $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), intval($a->profile['uid'])); + if ($r) + $item_id = $r[0]["id"]; + else + $item_id = false; + } + + if (!$item_id) { $a->error = 404; - notice( t('Item not found.') . EOL); + notice(t('Item not found.').EOL); return; } + $groups = array(); $contact = null; @@ -334,7 +344,7 @@ function display_content(&$a, $update = 0) { } } - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 1 LIMIT 1", + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1", intval($a->profile['uid']) ); if(count($r)) @@ -367,62 +377,53 @@ function display_content(&$a, $update = 0) { $sql_extra = item_permissions_sql($a->profile['uid'],$remote_contact,$groups); - // AND `item`.`parent` = ( SELECT `parent` FROM `item` FORCE INDEX (PRIMARY, `uri`) WHERE ( `id` = '%s' OR `uri` = '%s' )) - if($update) { - $r = q("SELECT id FROM item WHERE item.uid = %d - AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE (`id` = '%s' OR `uri` = '%s')) - $sql_extra AND unseen = 1", - intval($a->profile['uid']), - dbesc($item_id), - dbesc($item_id) + $r = q("SELECT `id` FROM `item` WHERE `item`.`uid` = %d + AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE `id` = %d) + $sql_extra AND `unseen`", + intval($a->profile['uid']), + intval($item_id) ); if(!$r) return ''; } - // AND `item`.`parent` = ( SELECT `parent` FROM `item` FORCE INDEX (PRIMARY, `uri`) WHERE ( `id` = '%s' OR `uri` = '%s' ) - $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`.`self`, `contact`.`writable`, `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 - and `item`.`moderated` = 0 - AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE (`id` = '%s' OR `uri` = '%s') - AND uid = %d) + AND NOT `contact`.`blocked` AND NOT `contact`.`pending` + WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` + AND NOT `item`.`moderated` + AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE `id` = %d) $sql_extra ORDER BY `parent` DESC, `gravity` ASC, `id` ASC", intval($a->profile['uid']), - dbesc($item_id), - dbesc($item_id), - intval($a->profile['uid']) + intval($item_id) ); if(!$r && local_user()) { // Check if this is another person's link to a post that we have $r = q("SELECT `item`.uri FROM `item` - WHERE (`item`.`id` = '%s' OR `item`.`uri` = '%s' ) + WHERE (`item`.`id` = %d OR `item`.`uri` = '%s') LIMIT 1", - dbesc($item_id), + intval($item_id), dbesc($item_id) ); if($r) { $item_uri = $r[0]['uri']; - // AND `item`.`parent` = ( SELECT `parent` FROM `item` FORCE INDEX (PRIMARY, `uri`) WHERE `uri` = '%s' AND uid = %d ) $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`.`self`, `contact`.`writable`, `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 - and `item`.`moderated` = 0 + AND NOT `contact`.`blocked` AND NOT `contact`.`pending` + WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` + AND NOT `item`.`moderated` AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE `uri` = '%s' AND uid = %d) ORDER BY `parent` DESC, `gravity` ASC, `id` ASC ", intval(local_user()), @@ -437,7 +438,7 @@ function display_content(&$a, $update = 0) { if((local_user()) && (local_user() == $a->profile['uid'])) { q("UPDATE `item` SET `unseen` = 0 - WHERE `parent` = %d AND `unseen` = 1", + WHERE `parent` = %d AND `unseen`", intval($r[0]['parent']) ); } From 07818a65536c1cb6373f4494245c583853e0fa95 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 13 Mar 2016 16:14:51 +0100 Subject: [PATCH 192/273] Decode function is now there as well. --- include/diaspora.php | 15 ++-- include/diaspora2.php | 168 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 174 insertions(+), 9 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 78ba520790..2b85befa8c 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -111,18 +111,18 @@ function diaspora_dispatch($importer,$msg,$attempt=1) { $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg); } elseif($xmlbase->retraction) { - $tempfile = tempnam(get_temppath(), "diaspora-retraction"); - file_put_contents($tempfile, json_encode($data)); + //$tempfile = tempnam(get_temppath(), "diaspora-retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); } elseif($xmlbase->signed_retraction) { - $tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); - file_put_contents($tempfile, json_encode($data)); + //$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); } elseif($xmlbase->relayable_retraction) { - $tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); - file_put_contents($tempfile, json_encode($data)); + //$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg); } elseif($xmlbase->photo) { @@ -468,6 +468,9 @@ EOT; function diaspora_decode($importer,$xml) { + $tempfile = tempnam(get_temppath(), "diaspora-decode"); + file_put_contents($tempfile, json_encode(array("importer" => $importer, "xml" => $xml))); + $public = false; $basedom = parse_xml_string($xml); diff --git a/include/diaspora2.php b/include/diaspora2.php index da772d68bd..081eaf153a 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -64,6 +64,168 @@ class diaspora { return $relay; } + function repair_signature($signature, $handle = "", $level = 1) { + + if ($signature == "") + return ($signature); + + if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) { + $signature = base64_decode($signature); + logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG); + + // Do a recursive call to be able to fix even multiple levels + if ($level < 10) + $signature = self::repair_signature($signature, $handle, ++$level); + } + + return($signature); + } + + /** + * @brief: Decodes incoming Diaspora message + * + * @param array $importer from user table + * @param string $xml urldecoded Diaspora salmon + * + * @return array + * 'message' -> decoded Diaspora XML message + * 'author' -> author diaspora handle + * 'key' -> author public key (converted to pkcs#8) + */ + function decode($importer, $xml) { + + $public = false; + $basedom = parse_xml_string($xml); + + if (!is_object($basedom)) + return false; + + $children = $basedom->children('https://joindiaspora.com/protocol'); + + if($children->header) { + $public = true; + $author_link = str_replace('acct:','',$children->header->author_id); + } else { + + $encrypted_header = json_decode(base64_decode($children->encrypted_header)); + + $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); + $ciphertext = base64_decode($encrypted_header->ciphertext); + + $outer_key_bundle = ''; + openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); + + $j_outer_key_bundle = json_decode($outer_key_bundle); + + $outer_iv = base64_decode($j_outer_key_bundle->iv); + $outer_key = base64_decode($j_outer_key_bundle->key); + + $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); + + + $decrypted = pkcs5_unpad($decrypted); + + /** + * $decrypted now contains something like + * + * <decrypted_header> + * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv> + * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key> + * <author_id>galaxor@diaspora.priateship.org</author_id> + * </decrypted_header> + */ + + logger('decrypted: '.$decrypted, LOGGER_DEBUG); + $idom = parse_xml_string($decrypted,false); + + $inner_iv = base64_decode($idom->iv); + $inner_aes_key = base64_decode($idom->aes_key); + + $author_link = str_replace('acct:','',$idom->author_id); + } + + $dom = $basedom->children(NAMESPACE_SALMON_ME); + + // figure out where in the DOM tree our data is hiding + + if($dom->provenance->data) + $base = $dom->provenance; + elseif($dom->env->data) + $base = $dom->env; + elseif($dom->data) + $base = $dom; + + if (!$base) { + logger('unable to locate salmon data in xml'); + http_status_exit(400); + } + + + // Stash the signature away for now. We have to find their key or it won't be good for anything. + $signature = base64url_decode($base->sig); + + // unpack the data + + // strip whitespace so our data element will return to one big base64 blob + $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); + + + // stash away some other stuff for later + + $type = $base->data[0]->attributes()->type[0]; + $keyhash = $base->sig[0]->attributes()->keyhash[0]; + $encoding = $base->encoding; + $alg = $base->alg; + + + $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg); + + + // decode the data + $data = base64url_decode($data); + + + if($public) + $inner_decrypted = $data; + else { + + // Decode the encrypted blob + + $inner_encrypted = base64_decode($data); + $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); + $inner_decrypted = pkcs5_unpad($inner_decrypted); + } + + if (!$author_link) { + logger('Could not retrieve author URI.'); + http_status_exit(400); + } + // Once we have the author URI, go to the web and try to find their public key + // (first this will look it up locally if it is in the fcontact cache) + // This will also convert diaspora public key from pkcs#1 to pkcs#8 + + logger('Fetching key for '.$author_link); + $key = self::key($author_link); + + if (!$key) { + logger('Could not retrieve author key.'); + http_status_exit(400); + } + + $verify = rsa_verify($signed_data,$signature,$key); + + if (!$verify) { + logger('Message did not verify. Discarding.'); + http_status_exit(400); + } + + logger('Message verified.'); + + return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); + + } + + /** * @brief Dispatches public messages and find the fitting receivers * @@ -1287,7 +1449,7 @@ class diaspora { $ret = self::person_by_handle($author); if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { - logger("Cannot resolve diaspora handle ".$author ." for ".$recipient); + logger("Cannot resolve diaspora handle ".$author." for ".$recipient); return false; } @@ -1854,7 +2016,7 @@ class diaspora { dbesc($slap), intval($public_batch) ); - if(count($r)) { + if($r) { logger("add_to_queue ignored - identical item already in queue"); } else { // queue message for redelivery @@ -2211,7 +2373,7 @@ class diaspora { intval($item["uid"]) ); - if (!count($r)) { + if (!$r) { logger("conversation not found."); return; } From 2841aa0281442e7a5570c122d5f3d9ab3a6504a7 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 13 Mar 2016 19:47:02 +0100 Subject: [PATCH 193/273] New implementation is now live. --- include/delivery.php | 182 +++++++++++++++++++++--------------------- include/diaspora.php | 1 + include/diaspora2.php | 49 ++++++++++-- mod/receive.php | 14 ++-- 4 files changed, 144 insertions(+), 102 deletions(-) diff --git a/include/delivery.php b/include/delivery.php index e5ca0946b3..d184fe12e1 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -10,11 +10,11 @@ require_once("include/dfrn.php"); function delivery_run(&$argv, &$argc){ global $a, $db; - if(is_null($a)){ + if (is_null($a)){ $a = new App; } - if(is_null($db)) { + if (is_null($db)) { @include(".htconfig.php"); require_once("include/dba.php"); $db = new dba($db_host, $db_user, $db_pass, $db_data); @@ -32,12 +32,12 @@ function delivery_run(&$argv, &$argc){ load_hooks(); - if($argc < 3) + if ($argc < 3) return; $a->set_baseurl(get_config('system','url')); - logger('delivery: invoked: ' . print_r($argv,true), LOGGER_DEBUG); + logger('delivery: invoked: '. print_r($argv,true), LOGGER_DEBUG); $cmd = $argv[1]; $item_id = intval($argv[2]); @@ -53,7 +53,7 @@ function delivery_run(&$argv, &$argc){ dbesc($item_id), dbesc($contact_id) ); - if(! count($r)) { + if (!count($r)) { continue; } @@ -68,7 +68,7 @@ function delivery_run(&$argv, &$argc){ dbesc($contact_id) ); - if((! $item_id) || (! $contact_id)) + if (!$item_id || !$contact_id) continue; $expire = false; @@ -84,20 +84,20 @@ function delivery_run(&$argv, &$argc){ $recipients[] = $contact_id; - if($cmd === 'mail') { + if ($cmd === 'mail') { $normal_mode = false; $mail = true; $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", intval($item_id) ); - if(! count($message)){ + if (!count($message)){ return; } $uid = $message[0]['uid']; $recipients[] = $message[0]['contact-id']; $item = $message[0]; } - elseif($cmd === 'expire') { + elseif ($cmd === 'expire') { $normal_mode = false; $expire = true; $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1 @@ -106,22 +106,22 @@ function delivery_run(&$argv, &$argc){ ); $uid = $item_id; $item_id = 0; - if(! count($items)) + if (!count($items)) continue; } - elseif($cmd === 'suggest') { + elseif ($cmd === 'suggest') { $normal_mode = false; $fsuggest = true; $suggest = q("SELECT * FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item_id) ); - if(! count($suggest)) + if (!count($suggest)) return; $uid = $suggest[0]['uid']; $recipients[] = $suggest[0]['cid']; $item = $suggest[0]; - } elseif($cmd === 'relocate') { + } elseif ($cmd === 'relocate') { $normal_mode = false; $relocate = true; $uid = $item_id; @@ -131,7 +131,7 @@ function delivery_run(&$argv, &$argc){ intval($item_id) ); - if((! count($r)) || (! intval($r[0]['parent']))) { + if ((!count($r)) || (!intval($r[0]['parent']))) { continue; } @@ -145,32 +145,32 @@ function delivery_run(&$argv, &$argc){ intval($parent_id) ); - if(! count($items)) { + if (!count($items)) { continue; } $icontacts = null; $contacts_arr = array(); foreach($items as $item) - if(! in_array($item['contact-id'],$contacts_arr)) + if (!in_array($item['contact-id'],$contacts_arr)) $contacts_arr[] = intval($item['contact-id']); - if(count($contacts_arr)) { + if (count($contacts_arr)) { $str_contacts = implode(',',$contacts_arr); $icontacts = q("SELECT * FROM `contact` WHERE `id` IN ( $str_contacts ) " ); } - if( ! ($icontacts && count($icontacts))) + if ( !($icontacts && count($icontacts))) continue; // avoid race condition with deleting entries - if($items[0]['deleted']) { + if ($items[0]['deleted']) { foreach($items as $item) $item['deleted'] = 1; } - if((count($items) == 1) && ($items[0]['uri'] === $items[0]['parent-uri'])) { + if ((count($items) == 1) && ($items[0]['uri'] === $items[0]['parent-uri'])) { logger('delivery: top level post'); $top_level = true; } @@ -184,7 +184,7 @@ function delivery_run(&$argv, &$argc){ intval($uid) ); - if(! count($r)) + if (!count($r)) continue; $owner = $r[0]; @@ -193,7 +193,7 @@ function delivery_run(&$argv, &$argc){ $public_message = true; - if(! ($mail || $fsuggest || $relocate)) { + if (!($mail || $fsuggest || $relocate)) { require_once('include/group.php'); $parent = $items[0]; @@ -217,7 +217,7 @@ function delivery_run(&$argv, &$argc){ $localhost = $a->get_hostname(); - if(strpos($localhost,':')) + if (strpos($localhost,':')) $localhost = substr($localhost,0,strpos($localhost,':')); /** @@ -230,17 +230,17 @@ function delivery_run(&$argv, &$argc){ $relay_to_owner = false; - if((! $top_level) && ($parent['wall'] == 0) && (! $expire) && (stristr($target_item['uri'],$localhost))) { + if (!$top_level && ($parent['wall'] == 0) && !$expire && stristr($target_item['uri'],$localhost)) { $relay_to_owner = true; } - if($relay_to_owner) { + if ($relay_to_owner) { logger('followup '.$target_item["guid"], LOGGER_DEBUG); // local followup to remote post $followup = true; } - if((strlen($parent['allow_cid'])) + if ((strlen($parent['allow_cid'])) || (strlen($parent['allow_gid'])) || (strlen($parent['deny_cid'])) || (strlen($parent['deny_gid']))) { @@ -253,10 +253,10 @@ function delivery_run(&$argv, &$argc){ intval($contact_id) ); - if(count($r)) + if (count($r)) $contact = $r[0]; - if($contact['self']) + if ($contact['self']) continue; $deliver_status = 0; @@ -266,7 +266,7 @@ function delivery_run(&$argv, &$argc){ switch($contact['network']) { case NETWORK_DFRN: - logger('notifier: '.$target_item["guid"].' dfrndelivery: ' . $contact['name']); + logger('notifier: '.$target_item["guid"].' dfrndelivery: '.$contact['name']); if ($mail) { $item['body'] = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']); @@ -276,13 +276,13 @@ function delivery_run(&$argv, &$argc){ q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id'])); } elseif ($relocate) $atom = dfrn::relocate($owner, $uid); - elseif($followup) { + elseif ($followup) { $msgitems = array(); foreach($items as $item) { // there is only one item - if(!$item['parent']) + if (!$item['parent']) continue; - if($item['id'] == $item_id) { - logger('followup: item: ' . print_r($item,true), LOGGER_DATA); + if ($item['id'] == $item_id) { + logger('followup: item: '. print_r($item,true), LOGGER_DATA); $msgitems[] = $item; } } @@ -290,19 +290,19 @@ function delivery_run(&$argv, &$argc){ } else { $msgitems = array(); foreach($items as $item) { - if(!$item['parent']) + if (!$item['parent']) continue; // private emails may be in included in public conversations. Filter them. - if(($public_message) && $item['private']) + if ($public_message && $item['private']) continue; $item_contact = get_item_contact($item,$icontacts); - if(!$item_contact) + if (!$item_contact) continue; - if($normal_mode) { - if($item_id == $item['id'] || $item['id'] == $item['parent']) { + if ($normal_mode) { + if ($item_id == $item['id'] || $item['id'] == $item['parent']) { $item["entry:comment-allow"] = true; $item["entry:cid"] = (($top_level) ? $contact['id'] : 0); $msgitems[] = $item; @@ -317,15 +317,15 @@ function delivery_run(&$argv, &$argc){ logger('notifier entry: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG); - logger('notifier: ' . $atom, LOGGER_DATA); + logger('notifier: '.$atom, LOGGER_DATA); $basepath = implode('/', array_slice(explode('/',$contact['url']),0,3)); // perform local delivery if we are on the same site - if(link_compare($basepath,$a->get_baseurl())) { + if (link_compare($basepath,$a->get_baseurl())) { $nickname = basename($contact['url']); - if($contact['issued-id']) + if ($contact['issued-id']) $sql_extra = sprintf(" AND `dfrn-id` = '%s' ", dbesc($contact['issued-id'])); else $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($contact['dfrn-id'])); @@ -347,10 +347,10 @@ function delivery_run(&$argv, &$argc){ dbesc($nickname) ); - if($x && count($x)) { + if ($x && count($x)) { $write_flag = ((($x[0]['rel']) && ($x[0]['rel'] != CONTACT_IS_SHARING)) ? true : false); - if((($owner['page-flags'] == PAGE_COMMUNITY) || ($write_flag)) && (! $x[0]['writable'])) { - q("update contact set writable = 1 where id = %d", + if ((($owner['page-flags'] == PAGE_COMMUNITY) || $write_flag) && !$x[0]['writable']) { + q("UPDATE `contact` SET `writable` = 1 WHERE `id` = %d", intval($x[0]['id']) ); $x[0]['writable'] = 1; @@ -370,14 +370,14 @@ function delivery_run(&$argv, &$argc){ } } - if(! was_recently_delayed($contact['id'])) + if (!was_recently_delayed($contact['id'])) $deliver_status = dfrn::deliver($owner,$contact,$atom); else $deliver_status = (-1); logger('notifier: dfrn_delivery to '.$contact["url"].' with guid '.$target_item["guid"].' returns '.$deliver_status); - if($deliver_status == (-1)) { + if ($deliver_status == (-1)) { logger('notifier: delivery failed: queuing message'); add_to_queue($contact['id'],NETWORK_DFRN,$atom); } @@ -385,9 +385,9 @@ function delivery_run(&$argv, &$argc){ case NETWORK_OSTATUS: // Do not send to otatus if we are not configured to send to public networks - if($owner['prvnets']) + if ($owner['prvnets']) break; - if(get_config('system','ostatus_disabled') || get_config('system','dfrn_only')) + if (get_config('system','ostatus_disabled') || get_config('system','dfrn_only')) break; // There is currently no code here to distribute anything to OStatus. @@ -397,67 +397,67 @@ function delivery_run(&$argv, &$argc){ case NETWORK_MAIL: case NETWORK_MAIL2: - if(get_config('system','dfrn_only')) + if (get_config('system','dfrn_only')) break; // WARNING: does not currently convert to RFC2047 header encodings, etc. $addr = $contact['addr']; - if(! strlen($addr)) + if (!strlen($addr)) break; - if($cmd === 'wall-new' || $cmd === 'comment-new') { + if ($cmd === 'wall-new' || $cmd === 'comment-new') { $it = null; - if($cmd === 'wall-new') + if ($cmd === 'wall-new') $it = $items[0]; else { $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($argv[2]), intval($uid) ); - if(count($r)) + if (count($r)) $it = $r[0]; } - if(! $it) + if (!$it) break; $local_user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid) ); - if(! count($local_user)) + if (!count($local_user)) break; $reply_to = ''; $r1 = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1", intval($uid) ); - if($r1 && $r1[0]['reply_to']) + if ($r1 && $r1[0]['reply_to']) $reply_to = $r1[0]['reply_to']; $subject = (($it['title']) ? email_header_encode($it['title'],'UTF-8') : t("\x28no subject\x29")) ; // only expose our real email address to true friends - if(($contact['rel'] == CONTACT_IS_FRIEND) && (! $contact['blocked'])) { - if($reply_to) { + if (($contact['rel'] == CONTACT_IS_FRIEND) && !$contact['blocked']) { + if ($reply_to) { $headers = 'From: '.email_header_encode($local_user[0]['username'],'UTF-8').' <'.$reply_to.'>'."\n"; $headers .= 'Sender: '.$local_user[0]['email']."\n"; } else $headers = 'From: '.email_header_encode($local_user[0]['username'],'UTF-8').' <'.$local_user[0]['email'].'>'."\n"; } else - $headers = 'From: ' . email_header_encode($local_user[0]['username'],'UTF-8') . ' <' . t('noreply') . '@' . $a->get_hostname() . '>' . "\n"; + $headers = 'From: '. email_header_encode($local_user[0]['username'],'UTF-8') .' <'. t('noreply') .'@'.$a->get_hostname() .'>'. "\n"; - //if($reply_to) - // $headers .= 'Reply-to: ' . $reply_to . "\n"; + //if ($reply_to) + // $headers .= 'Reply-to: '.$reply_to . "\n"; - $headers .= 'Message-Id: <' . iri2msgid($it['uri']). '>' . "\n"; + $headers .= 'Message-Id: <'. iri2msgid($it['uri']).'>'. "\n"; //logger("Mail: uri: ".$it['uri']." parent-uri ".$it['parent-uri'], LOGGER_DEBUG); //logger("Mail: Data: ".print_r($it, true), LOGGER_DEBUG); //logger("Mail: Data: ".print_r($it, true), LOGGER_DATA); - if($it['uri'] !== $it['parent-uri']) { + if ($it['uri'] !== $it['parent-uri']) { $headers .= "References: <".iri2msgid($it["parent-uri"]).">"; // If Threading is enabled, write down the correct parent @@ -465,23 +465,23 @@ function delivery_run(&$argv, &$argc){ $headers .= " <".iri2msgid($it["thr-parent"]).">"; $headers .= "\n"; - if(!$it['title']) { + if (!$it['title']) { $r = q("SELECT `title` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); - if(count($r) AND ($r[0]['title'] != '')) + if (count($r) AND ($r[0]['title'] != '')) $subject = $r[0]['title']; else { $r = q("SELECT `title` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); - if(count($r) AND ($r[0]['title'] != '')) + if (count($r) AND ($r[0]['title'] != '')) $subject = $r[0]['title']; } } - if(strncasecmp($subject,'RE:',3)) + if (strncasecmp($subject,'RE:',3)) $subject = 'Re: '.$subject; } email_send($addr, $subject, $headers, $it); @@ -489,60 +489,64 @@ function delivery_run(&$argv, &$argc){ break; case NETWORK_DIASPORA: - if($public_message) - $loc = 'public batch ' . $contact['batch']; + if ($public_message) + $loc = 'public batch '.$contact['batch']; else $loc = $contact['name']; - logger('delivery: diaspora batch deliver: ' . $loc); + logger('delivery: diaspora batch deliver: '.$loc); - if(get_config('system','dfrn_only') || (!get_config('system','diaspora_enabled'))) + if (get_config('system','dfrn_only') || (!get_config('system','diaspora_enabled'))) break; - if($mail) { - diaspora_send_mail($item,$owner,$contact); + if ($mail) { + diaspora::send_mail($item,$owner,$contact); + //diaspora_send_mail($item,$owner,$contact); break; } - if(!$normal_mode) + if (!$normal_mode) break; - if((! $contact['pubkey']) && (! $public_message)) + if (!$contact['pubkey'] && !$public_message) break; $unsupported_activities = array(ACTIVITY_DISLIKE, ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE); //don't transmit activities which are not supported by diaspora foreach($unsupported_activities as $act) { - if(activity_match($target_item['verb'],$act)) { + if (activity_match($target_item['verb'],$act)) { break 2; } } - if(($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) { + if (($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) { // top-level retraction - logger('delivery: diaspora retract: ' . $loc); + logger('delivery: diaspora retract: '.$loc); - diaspora_send_retraction($target_item,$owner,$contact,$public_message); + diaspora::send_retraction($target_item,$owner,$contact,$public_message); + //diaspora_send_retraction($target_item,$owner,$contact,$public_message); break; - } elseif($followup) { + } elseif ($followup) { // send comments and likes to owner to relay - diaspora_send_followup($target_item,$owner,$contact,$public_message); + diaspora::send_followup($target_item,$owner,$contact,$public_message); + //diaspora_send_followup($target_item,$owner,$contact,$public_message); break; - } elseif($target_item['uri'] !== $target_item['parent-uri']) { + } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions to our conversants - logger('delivery: diaspora relay: ' . $loc); - - diaspora_send_relay($target_item,$owner,$contact,$public_message); + logger('delivery: diaspora relay: '.$loc); + diaspora::send_relay($target_item,$owner,$contact,$public_message); + //diaspora_send_relay($target_item,$owner,$contact,$public_message); break; - } elseif(($top_level) && (! $walltowall)) { + } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall - logger('delivery: diaspora status: ' . $loc); - diaspora_send_status($target_item,$owner,$contact,$public_message); + logger('delivery: diaspora status: '.$loc); + diaspora::send_status($target_item,$owner,$contact,$public_message); + //diaspora_send_status($target_item,$owner,$contact,$public_message); break; } - logger('delivery: diaspora unknown mode: ' . $contact['name']); + logger('delivery: diaspora unknown mode: '.$contact['name']); break; diff --git a/include/diaspora.php b/include/diaspora.php index 2b85befa8c..11fe2c9b57 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -15,6 +15,7 @@ require_once('include/lock.php'); require_once('include/threads.php'); require_once('mod/share.php'); require_once('include/enotify.php'); +require_once('include/diaspora2.php'); function diaspora_dispatch_public($msg) { diff --git a/include/diaspora2.php b/include/diaspora2.php index 081eaf153a..b031651675 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -284,6 +284,8 @@ class diaspora { $type = $fields->getName(); + logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG); + switch ($type) { case "account_deletion": return self::receive_account_deletion($importer, $fields); @@ -654,7 +656,7 @@ class diaspora { return false; } - if (!self::post_allow($importer, $contact, false)) { + if (!self::post_allow($importer, $contact, $is_comment)) { logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]); return false; } @@ -669,10 +671,10 @@ class diaspora { if($r) { logger("message ".$guid." already exists for user ".$uid); - return false; + return true; } - return true; + return false; } private function fetch_guid($item) { @@ -774,10 +776,12 @@ class diaspora { } if (!$r) { - logger("parent item not found: parent: ".$guid." item: ".$guid); + logger("parent item not found: parent: ".$guid." - user: ".$uid); return false; - } else + } else { + logger("parent item found: parent: ".$guid." - user: ".$uid); return $r[0]; + } } private function author_contact_by_url($contact, $person, $uid) { @@ -892,6 +896,9 @@ class diaspora { $message_id = item_store($datarray); + if ($message_id) + logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1175,6 +1182,9 @@ class diaspora { $message_id = item_store($datarray); + if ($message_id) + logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1358,6 +1368,8 @@ class diaspora { update_gcontact($gcontact); + logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG); + return true; } @@ -1654,6 +1666,9 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); + if ($message_id) + logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + return $message_id; } @@ -1695,6 +1710,8 @@ class diaspora { ); delete_thread($r[0]["id"], $r[0]["parent-uri"]); + logger("Deleted target ".$target_guid." from user ".$importer["uid"], LOGGER_DEBUG); + // Now check if the retraction needs to be relayed by us if($p[0]["origin"]) { @@ -1822,7 +1839,8 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - logger("Stored item with message id ".$message_id, LOGGER_DEBUG); + if ($message_id) + logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); return $message_id; } @@ -2329,8 +2347,21 @@ class diaspora { /// @todo Change all signatur storing functions to the new format if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) $message = self::message_from_signatur($item, $signature); - else // New way - $message = json_decode($signature['signed_text']); + else {// New way + $msg = json_decode($signature['signed_text'], true); + + $message = array(); + foreach ($msg AS $field => $data) { + if (!$item["deleted"]) { + if ($field == "author") + $field = "diaspora_handle"; + if ($field == "parent_type") + $field = "target_type"; + } + + $message[$field] = $data; + } + } if ($item["deleted"]) { $signed_text = $message["target_guid"].';'.$message["target_type"]; @@ -2338,6 +2369,8 @@ class diaspora { } else $message["parent_author_signature"] = self::signature($owner, $message); + logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } diff --git a/mod/receive.php b/mod/receive.php index 95a5101675..051ea8c25c 100644 --- a/mod/receive.php +++ b/mod/receive.php @@ -53,7 +53,8 @@ function receive_post(&$a) { logger('mod-diaspora: message is okay', LOGGER_DEBUG); - $msg = diaspora_decode($importer,$xml); + $msg = diaspora::decode($importer,$xml); + //$msg = diaspora_decode($importer,$xml); logger('mod-diaspora: decoded', LOGGER_DEBUG); @@ -65,10 +66,13 @@ function receive_post(&$a) { logger('mod-diaspora: dispatching', LOGGER_DEBUG); $ret = 0; - if($public) - diaspora_dispatch_public($msg); - else - $ret = diaspora_dispatch($importer,$msg); + if($public) { + diaspora::dispatch_public($msg); + //diaspora_dispatch_public($msg); + } else { + $ret = diaspora::dispatch($importer,$msg); + //$ret = diaspora_dispatch($importer,$msg); + } http_status_exit(($ret) ? $ret : 200); // NOTREACHED From 75f5cfe63e2d84fbe55384a7b2769c55bbf423b6 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 13 Mar 2016 21:11:48 +0100 Subject: [PATCH 194/273] Retraction and reshares work --- include/diaspora2.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index b031651675..f6b8b9a704 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1628,6 +1628,8 @@ class diaspora { if (!$original_item) return false; + $orig_url = App::get_baseurl()."/display/".$original_item["guid"]; + $datarray = array(); $datarray["uid"] = $importer["uid"]; @@ -1651,7 +1653,7 @@ class diaspora { $datarray["object"] = json_encode($data); $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], - $original_item["guid"], $original_item["created"], $original_item["uri"]); + $original_item["guid"], $original_item["created"], $orig_url); $datarray["body"] = $prefix.$original_item["body"]."[/share]"; $datarray["tag"] = $original_item["tag"]; @@ -1691,16 +1693,20 @@ class diaspora { return false; // Only delete it if the author really fits - if (!link_compare($r[0]["author-link"],$person["url"])) + if (!link_compare($r[0]["author-link"], $person["url"])) { + logger("Item author ".$r[0]["author-link"]." doesn't fit to expected contact ".$person["url"], LOGGER_DEBUG); return false; + } // Check if the sender is the thread owner $p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d", intval($r[0]["parent"])); // Only delete it if the parent author really fits - if (!link_compare($p[0]["author-link"], $contact["url"])) + if (!link_compare($p[0]["author-link"], $contact["url"]) AND !link_compare($r[0]["author-link"], $contact["url"])) { + logger("Thread author ".$p[0]["author-link"]." and item author ".$r[0]["author-link"]." don't fit to expected contact ".$contact["url"], LOGGER_DEBUG); return false; + } // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d", @@ -1736,6 +1742,8 @@ class diaspora { return false; } + logger("Got retraction for ".$target_type.", sender ".$sender." and user ".$importer["uid"], LOGGER_DEBUG); + switch ($target_type) { case "Comment": case "Like": From 9ae2b2292fd07a53f46689f778cd4630031912a7 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 14 Mar 2016 08:11:14 +0100 Subject: [PATCH 195/273] Some more function calls changed to the new class --- include/Contact.php | 2 +- include/contact_selectors.php | 2 +- include/delivery.php | 8 ++++---- include/diaspora2.php | 10 ++++++++-- include/follow.php | 2 +- include/notifier.php | 6 +++--- include/queue.php | 2 +- 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/include/Contact.php b/include/Contact.php index 3799e0b189..d76c8f826c 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -129,7 +129,7 @@ function terminate_friendship($user,$self,$contact) { } elseif($contact['network'] === NETWORK_DIASPORA) { require_once('include/diaspora.php'); - diaspora_unshare($user,$contact); + diaspora::send_unshare($user,$contact); } elseif($contact['network'] === NETWORK_DFRN) { require_once('include/dfrn.php'); diff --git a/include/contact_selectors.php b/include/contact_selectors.php index a884a6b52b..3bf68f764e 100644 --- a/include/contact_selectors.php +++ b/include/contact_selectors.php @@ -99,7 +99,7 @@ function network_to_name($s, $profile = "") { $networkname = str_replace($search,$replace,$s); - if (($s == NETWORK_DIASPORA) AND ($profile != "") AND diaspora_is_redmatrix($profile)) { + if (($s == NETWORK_DIASPORA) AND ($profile != "") AND diaspora::is_redmatrix($profile)) { $networkname = t("Hubzilla/Redmatrix"); $r = q("SELECT `gserver`.`platform` FROM `gcontact` diff --git a/include/delivery.php b/include/delivery.php index d184fe12e1..1e1dadcd93 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -522,25 +522,25 @@ function delivery_run(&$argv, &$argc){ if (($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) { // top-level retraction - logger('delivery: diaspora retract: '.$loc); - + logger('diaspora retract: '.$loc); diaspora::send_retraction($target_item,$owner,$contact,$public_message); //diaspora_send_retraction($target_item,$owner,$contact,$public_message); break; } elseif ($followup) { // send comments and likes to owner to relay + logger('diaspora followup: '.$loc); diaspora::send_followup($target_item,$owner,$contact,$public_message); //diaspora_send_followup($target_item,$owner,$contact,$public_message); break; } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions to our conversants - logger('delivery: diaspora relay: '.$loc); + logger('diaspora relay: '.$loc); diaspora::send_relay($target_item,$owner,$contact,$public_message); //diaspora_send_relay($target_item,$owner,$contact,$public_message); break; } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall - logger('delivery: diaspora status: '.$loc); + logger('diaspora status: '.$loc); diaspora::send_status($target_item,$owner,$contact,$public_message); //diaspora_send_status($target_item,$owner,$contact,$public_message); break; diff --git a/include/diaspora2.php b/include/diaspora2.php index f6b8b9a704..5c17754e80 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -460,6 +460,8 @@ class diaspora { * @return string The public key */ private function key($handle) { + $handle = strval($handle); + logger("Fetching diaspora key for: ".$handle); $r = self::person_by_handle($handle); @@ -1699,7 +1701,7 @@ class diaspora { } // Check if the sender is the thread owner - $p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d", + $p = q("SELECT `id`, `author-link`, `origin` FROM `item` WHERE `id` = %d", intval($r[0]["parent"])); // Only delete it if the parent author really fits @@ -1716,7 +1718,7 @@ class diaspora { ); delete_thread($r[0]["id"], $r[0]["parent-uri"]); - logger("Deleted target ".$target_guid." from user ".$importer["uid"], LOGGER_DEBUG); + logger("Deleted target ".$target_guid." (".$r[0]["id"].") from user ".$importer["uid"]." parent: ".$p[0]["id"], LOGGER_DEBUG); // Now check if the retraction needs to be relayed by us if($p[0]["origin"]) { @@ -1727,6 +1729,8 @@ class diaspora { intval($r[0]["id"]), dbesc(json_encode($data)) ); + $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); + logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); // notify others proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); @@ -2341,6 +2345,8 @@ class diaspora { $type = "comment"; } + logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + // fetch the original signature $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", diff --git a/include/follow.php b/include/follow.php index 3af629536d..6eab7e12fa 100644 --- a/include/follow.php +++ b/include/follow.php @@ -303,7 +303,7 @@ function new_contact($uid,$url,$interactive = false) { } if($contact['network'] == NETWORK_DIASPORA) { require_once('include/diaspora.php'); - $ret = diaspora_share($a->user,$contact); + $ret = diaspora::send_share($a->user,$contact); logger('mod_follow: diaspora_share returns: ' . $ret); } } diff --git a/include/notifier.php b/include/notifier.php index 6c42f19c6a..e65da3adf2 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -536,7 +536,7 @@ function notifier_run(&$argv, &$argc){ if($public_message) { if (!$followup AND $top_level) - $r0 = diaspora_fetch_relay(); + $r0 = diaspora::relay_list(); else $r0 = array(); @@ -629,11 +629,11 @@ function notifier_run(&$argv, &$argc){ } // If the item was deleted, clean up the `sign` table - if($target_item['deleted']) { + /* if($target_item['deleted']) { $r = q("DELETE FROM sign where `retract_iid` = %d", intval($target_item['id']) ); - } + } */ logger('notifier: calling hooks', LOGGER_DEBUG); diff --git a/include/queue.php b/include/queue.php index 183ce0f9cd..878c149731 100644 --- a/include/queue.php +++ b/include/queue.php @@ -193,7 +193,7 @@ function queue_run(&$argv, &$argc){ case NETWORK_DIASPORA: if($contact['notify']) { logger('queue: diaspora_delivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>'); - $deliver_status = diaspora_transmit($owner,$contact,$data,$public,true); + $deliver_status = diaspora::transmit($owner,$contact,$data,$public,true); if($deliver_status == (-1)) { update_queue_time($q_item['id']); From 3734555715ab780c1d951c7c9d59c91b76b17b39 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 14 Mar 2016 20:04:17 +0100 Subject: [PATCH 196/273] Conversations are working now too --- include/diaspora2.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 5c17754e80..c0e054c384 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -918,7 +918,7 @@ class diaspora { return $message_id; } - private function receive_conversation_message($importer, $contact, $data, $msg, $mesg) { + private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); $author = notags(unxmlify($data->author)); @@ -1086,7 +1086,7 @@ class diaspora { } foreach($messages as $mesg) - self::receive_conversation_message($importer, $contact, $data, $msg, $mesg); + self::receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation); return true; } From 56cb6cc8972f55d82030c8c2b908b05e656640f6 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 14 Mar 2016 20:53:44 +0100 Subject: [PATCH 197/273] Removed just more old diaspora function calls. --- database.sql | 11 ------- doc/database.md | 1 - doc/database/db_dsprphotoq.md | 11 ------- include/cron.php | 4 --- include/dbstructure.php | 11 ------- include/delivery.php | 5 ---- include/diaspora2.php | 2 +- include/dsprphotoq.php | 55 ----------------------------------- include/follow.php | 2 +- mod/dfrn_confirm.php | 4 +-- mod/p.php | 6 ++-- mod/receive.php | 3 -- object/Item.php | 2 +- 13 files changed, 8 insertions(+), 109 deletions(-) delete mode 100644 doc/database/db_dsprphotoq.md delete mode 100644 include/dsprphotoq.php diff --git a/database.sql b/database.sql index 25faf0f4c0..02e5c9b378 100644 --- a/database.sql +++ b/database.sql @@ -201,17 +201,6 @@ CREATE TABLE IF NOT EXISTS `deliverq` ( PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; --- --- TABLE dsprphotoq --- -CREATE TABLE IF NOT EXISTS `dsprphotoq` ( - `id` int(10) unsigned NOT NULL auto_increment, - `uid` int(11) NOT NULL DEFAULT 0, - `msg` mediumtext NOT NULL, - `attempt` tinyint(4) NOT NULL DEFAULT 0, - PRIMARY KEY(`id`) -) DEFAULT CHARSET=utf8; - -- -- TABLE event -- diff --git a/doc/database.md b/doc/database.md index e37df05e09..f48404c17d 100644 --- a/doc/database.md +++ b/doc/database.md @@ -15,7 +15,6 @@ Database Tables | [contact](help/database/db_contact) | contact table | | [conv](help/database/db_conv) | private messages | | [deliverq](help/database/db_deliverq) | | -| [dsprphotoq](help/database/db_dsprphotoq) | | | [event](help/database/db_event) | Events | | [fcontact](help/database/db_fcontact) | friend suggestion stuff | | [ffinder](help/database/db_ffinder) | friend suggestion stuff | diff --git a/doc/database/db_dsprphotoq.md b/doc/database/db_dsprphotoq.md deleted file mode 100644 index 6af4d030e0..0000000000 --- a/doc/database/db_dsprphotoq.md +++ /dev/null @@ -1,11 +0,0 @@ -Table dsprphotoq -================ - -| Field | Description | Type | Null | Key | Default | Extra | -|---------|------------------|------------------|------|-----|---------|----------------| -| id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment | -| uid | | int(11) | NO | | 0 | | -| msg | | mediumtext | NO | | NULL | | -| attempt | | tinyint(4) | NO | | 0 | | - -Return to [database documentation](help/database) diff --git a/include/cron.php b/include/cron.php index db7d44be0b..60c62786e6 100644 --- a/include/cron.php +++ b/include/cron.php @@ -71,10 +71,6 @@ function cron_run(&$argv, &$argc){ proc_run('php',"include/queue.php"); - // run diaspora photo queue process in the background - - proc_run('php',"include/dsprphotoq.php"); - // run the process to discover global contacts in the background proc_run('php',"include/discover_poco.php"); diff --git a/include/dbstructure.php b/include/dbstructure.php index ddf036f2c1..e5e748bb24 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -537,17 +537,6 @@ function db_definition() { "PRIMARY" => array("id"), ) ); - $database["dsprphotoq"] = array( - "fields" => array( - "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), - "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"), - "msg" => array("type" => "mediumtext", "not null" => "1"), - "attempt" => array("type" => "tinyint(4)", "not null" => "1", "default" => "0"), - ), - "indexes" => array( - "PRIMARY" => array("id"), - ) - ); $database["event"] = array( "fields" => array( "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), diff --git a/include/delivery.php b/include/delivery.php index 1e1dadcd93..9ac9f2391b 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -501,7 +501,6 @@ function delivery_run(&$argv, &$argc){ if ($mail) { diaspora::send_mail($item,$owner,$contact); - //diaspora_send_mail($item,$owner,$contact); break; } @@ -524,25 +523,21 @@ function delivery_run(&$argv, &$argc){ // top-level retraction logger('diaspora retract: '.$loc); diaspora::send_retraction($target_item,$owner,$contact,$public_message); - //diaspora_send_retraction($target_item,$owner,$contact,$public_message); break; } elseif ($followup) { // send comments and likes to owner to relay logger('diaspora followup: '.$loc); diaspora::send_followup($target_item,$owner,$contact,$public_message); - //diaspora_send_followup($target_item,$owner,$contact,$public_message); break; } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions to our conversants logger('diaspora relay: '.$loc); diaspora::send_relay($target_item,$owner,$contact,$public_message); - //diaspora_send_relay($target_item,$owner,$contact,$public_message); break; } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall logger('diaspora status: '.$loc); diaspora::send_status($target_item,$owner,$contact,$public_message); - //diaspora_send_status($target_item,$owner,$contact,$public_message); break; } diff --git a/include/diaspora2.php b/include/diaspora2.php index c0e054c384..1a354e9cf2 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2093,7 +2093,7 @@ class diaspora { return self::build_and_transmit($owner, $contact, "retraction", $message); } - private function is_reshare($body) { + public static function is_reshare($body) { $body = trim($body); // Skip if it isn't a pure repeated messages diff --git a/include/dsprphotoq.php b/include/dsprphotoq.php deleted file mode 100644 index 0d8088d4bd..0000000000 --- a/include/dsprphotoq.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -require_once("boot.php"); -require_once('include/diaspora.php'); - -function dsprphotoq_run($argv, $argc){ - global $a, $db; - - if(is_null($a)){ - $a = new App; - } - - if(is_null($db)){ - @include(".htconfig.php"); - require_once("include/dba.php"); - $db = new dba($db_host, $db_user, $db_pass, $db_data); - unset($db_host, $db_user, $db_pass, $db_data); - }; - - logger("diaspora photo queue: running", LOGGER_DEBUG); - - $r = q("SELECT * FROM dsprphotoq"); - if(!$r) - return; - - $dphotos = $r; - - logger("diaspora photo queue: processing " . count($dphotos) . " photos"); - - foreach($dphotos as $dphoto) { - - $r = array(); - - if ($dphoto['uid'] == 0) - $r[0] = array("uid" => 0, "page-flags" => PAGE_FREELOVE); - else - $r = q("SELECT * FROM user WHERE uid = %d", - intval($dphoto['uid'])); - - if(!$r) { - logger("diaspora photo queue: user " . $dphoto['uid'] . " not found"); - return; - } - - $ret = diaspora_dispatch($r[0],unserialize($dphoto['msg']),$dphoto['attempt']); - q("DELETE FROM dsprphotoq WHERE id = %d", - intval($dphoto['id']) - ); - } -} - - -if (array_search(__file__,get_included_files())===0){ - dsprphotoq_run($_SERVER["argv"],$_SERVER["argc"]); - killme(); -} diff --git a/include/follow.php b/include/follow.php index 6eab7e12fa..d0411a466a 100644 --- a/include/follow.php +++ b/include/follow.php @@ -304,7 +304,7 @@ function new_contact($uid,$url,$interactive = false) { if($contact['network'] == NETWORK_DIASPORA) { require_once('include/diaspora.php'); $ret = diaspora::send_share($a->user,$contact); - logger('mod_follow: diaspora_share returns: ' . $ret); + logger('share returns: '.$ret); } } diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php index 68950ec285..cc09021dca 100644 --- a/mod/dfrn_confirm.php +++ b/mod/dfrn_confirm.php @@ -427,8 +427,8 @@ function dfrn_confirm_post(&$a,$handsfree = null) { if(($contact) && ($contact['network'] === NETWORK_DIASPORA)) { require_once('include/diaspora.php'); - $ret = diaspora_share($user[0],$r[0]); - logger('mod_follow: diaspora_share returns: ' . $ret); + $ret = diaspora::send_share($user[0],$r[0]); + logger('share returns: ' . $ret); } // Send a new friend post if we are allowed to... diff --git a/mod/p.php b/mod/p.php index 92b72dc1ce..20d6cfdbaf 100644 --- a/mod/p.php +++ b/mod/p.php @@ -28,14 +28,14 @@ function p_init($a){ $post = array(); - $reshared = diaspora_is_reshare($item[0]["body"]); + $reshared = diaspora::is_reshare($item[0]["body"]); if ($reshared) { $nodename = "reshare"; $post["root_diaspora_id"] = $reshared["root_handle"]; $post["root_guid"] = $reshared["root_guid"]; $post["guid"] = $item[0]["guid"]; - $post["diaspora_handle"] = diaspora_handle_from_contact($item[0]["contact-id"]); + $post["diaspora_handle"] = diaspora::handle_from_contact($item[0]["contact-id"]); $post["public"] = (!$item[0]["private"] ? 'true':'false'); $post["created_at"] = datetime_convert('UTC','UTC',$item[0]["created"]); } else { @@ -48,7 +48,7 @@ function p_init($a){ $nodename = "status_message"; $post["raw_message"] = str_replace("&", "&", $body); $post["guid"] = $item[0]["guid"]; - $post["diaspora_handle"] = diaspora_handle_from_contact($item[0]["contact-id"]); + $post["diaspora_handle"] = diaspora::handle_from_contact($item[0]["contact-id"]); $post["public"] = (!$item[0]["private"] ? 'true':'false'); $post["created_at"] = datetime_convert('UTC','UTC',$item[0]["created"]); $post["provider_display_name"] = $item[0]["app"]; diff --git a/mod/receive.php b/mod/receive.php index 051ea8c25c..4991ac47e8 100644 --- a/mod/receive.php +++ b/mod/receive.php @@ -54,7 +54,6 @@ function receive_post(&$a) { logger('mod-diaspora: message is okay', LOGGER_DEBUG); $msg = diaspora::decode($importer,$xml); - //$msg = diaspora_decode($importer,$xml); logger('mod-diaspora: decoded', LOGGER_DEBUG); @@ -68,10 +67,8 @@ function receive_post(&$a) { $ret = 0; if($public) { diaspora::dispatch_public($msg); - //diaspora_dispatch_public($msg); } else { $ret = diaspora::dispatch($importer,$msg); - //$ret = diaspora_dispatch($importer,$msg); } http_status_exit(($ret) ? $ret : 200); diff --git a/object/Item.php b/object/Item.php index e9c96cf159..59659cdaff 100644 --- a/object/Item.php +++ b/object/Item.php @@ -324,7 +324,7 @@ class Item extends BaseObject { // Diaspora isn't able to do likes on comments - but red does if (($item["item_network"] == NETWORK_DIASPORA) AND ($indent == 'comment') AND - !diaspora_is_redmatrix($item["owner-link"]) AND isset($buttons["like"])) + !diaspora::is_redmatrix($item["owner-link"]) AND isset($buttons["like"])) unset($buttons["like"]); // Diaspora doesn't has multithreaded comments From 6f43d3a4c4f87dd282691d3e65c75b0ed99a1b3b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 14 Mar 2016 22:10:26 +0100 Subject: [PATCH 198/273] Profile update is now working with the new function as well --- include/diaspora2.php | 86 ++++++++++++++++++++++++++++++++++- include/profile_update.php | 92 +------------------------------------- 2 files changed, 85 insertions(+), 93 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 1a354e9cf2..75cedeccd1 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2058,7 +2058,7 @@ class diaspora { } - private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "") { + private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { $data = array("XML" => array("post" => array($type => $message))); @@ -2069,7 +2069,11 @@ class diaspora { $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); + if ($spool) { + add_to_queue($contact['id'], NETWORK_DIASPORA, $slap, $public_batch); + return true; + } else + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); @@ -2467,5 +2471,83 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); } + + public static function send_profile($uid) { + + if (!$uid) + return; + + $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' + AND `uid` = %d AND `rel` != %d", + dbesc(NETWORK_DIASPORA), + intval($uid), + intval(CONTACT_IS_SHARING) + ); + if (!$recips) + return; + + $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr` + FROM `profile` + INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` + INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid` + WHERE `user`.`uid` = %d AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", + intval($uid) + ); + + if (!$r) + return; + + $profile = $r[0]; + + $handle = $profile["addr"]; + $first = ((strpos($profile['name'],' ') + ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); + $last = (($first === $profile['name']) ? '' : trim(substr($profile['name'], strlen($first)))); + $large = App::get_baseurl().'/photo/custom/300/'.$profile['uid'].'.jpg'; + $medium = App::get_baseurl().'/photo/custom/100/'.$profile['uid'].'.jpg'; + $small = App::get_baseurl().'/photo/custom/50/' .$profile['uid'].'.jpg'; + $searchable = (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false'); + + if ($searchable === 'true') { + $dob = '1000-00-00'; + + if (($profile['dob']) && ($profile['dob'] != '0000-00-00')) + $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') .'-'. datetime_convert('UTC','UTC',$profile['dob'],'m-d'); + + $about = $profile['about']; + $about = strip_tags(bbcode($about)); + + $location = formatted_location($profile); + $tags = ''; + if ($profile['pub_keywords']) { + $kw = str_replace(',',' ',$profile['pub_keywords']); + $kw = str_replace(' ',' ',$kw); + $arr = explode(' ',$profile['pub_keywords']); + if (count($arr)) { + for($x = 0; $x < 5; $x ++) { + if (trim($arr[$x])) + $tags .= '#'. trim($arr[$x]) .' '; + } + } + } + $tags = trim($tags); + } + + $message = array("diaspora_handle" => $handle, + "first_name" => $first, + "last_name" => $last, + "image_url" => $large, + "image_url_medium" => $medium, + "image_url_small" => $small, + "birthday" => $dob, + "gender" => $profile['gender'], + "bio" => $about, + "location" => $location, + "searchable" => $searchable, + "tag_string" => $tags); + + foreach($recips as $recip) + self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); + } } ?> diff --git a/include/profile_update.php b/include/profile_update.php index 7cc72cc866..399150f21c 100644 --- a/include/profile_update.php +++ b/include/profile_update.php @@ -1,96 +1,6 @@ <?php - -require_once('include/datetime.php'); require_once('include/diaspora.php'); -require_once('include/queue_fn.php'); -require_once('include/Contact.php'); function profile_change() { - - $a = get_app(); - - if(! local_user()) - return; - -// $url = $a->get_baseurl() . '/profile/' . $a->user['nickname']; -// if($url && strlen(get_config('system','directory'))) -// proc_run('php',"include/directory.php","$url"); - - $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' - AND `uid` = %d AND `rel` != %d ", - dbesc(NETWORK_DIASPORA), - intval(local_user()), - intval(CONTACT_IS_SHARING) - ); - if(! count($recips)) - return; - - $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` - INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` - WHERE `user`.`uid` = %d AND `profile`.`is-default` = 1 LIMIT 1", - intval(local_user()) - ); - - if(! count($r)) - return; - $profile = $r[0]; - - $handle = xmlify($a->user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3)); - $first = xmlify(((strpos($profile['name'],' ')) - ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); - $last = xmlify((($first === $profile['name']) ? '' : trim(substr($profile['name'],strlen($first))))); - $large = xmlify($a->get_baseurl() . '/photo/custom/300/' . $profile['uid'] . '.jpg'); - $medium = xmlify($a->get_baseurl() . '/photo/custom/100/' . $profile['uid'] . '.jpg'); - $small = xmlify($a->get_baseurl() . '/photo/custom/50/' . $profile['uid'] . '.jpg'); - $searchable = xmlify((($profile['publish'] && $profile['net-publish']) ? 'true' : 'false' )); -// $searchable = 'true'; - - if($searchable === 'true') { - $dob = '1000-00-00'; - - if(($profile['dob']) && ($profile['dob'] != '0000-00-00')) - $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') . '-' . datetime_convert('UTC','UTC',$profile['dob'],'m-d'); - $gender = xmlify($profile['gender']); - $about = xmlify($profile['about']); - require_once('include/bbcode.php'); - $about = xmlify(strip_tags(bbcode($about))); - $location = formatted_location($profile); - $location = xmlify($location); - $tags = ''; - if($profile['pub_keywords']) { - $kw = str_replace(',',' ',$profile['pub_keywords']); - $kw = str_replace(' ',' ',$kw); - $arr = explode(' ',$profile['pub_keywords']); - if(count($arr)) { - for($x = 0; $x < 5; $x ++) { - if(trim($arr[$x])) - $tags .= '#' . trim($arr[$x]) . ' '; - } - } - } - $tags = xmlify(trim($tags)); - } - - $tpl = get_markup_template('diaspora_profile.tpl'); - - $msg = replace_macros($tpl,array( - '$handle' => $handle, - '$first' => $first, - '$last' => $last, - '$large' => $large, - '$medium' => $medium, - '$small' => $small, - '$dob' => $dob, - '$gender' => $gender, - '$about' => $about, - '$location' => $location, - '$searchable' => $searchable, - '$tags' => $tags - )); - logger('profile_change: ' . $msg, LOGGER_ALL); - - foreach($recips as $recip) { - $msgtosend = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$a->user,$recip,$a->user['prvkey'],$recip['pubkey'],false))); - add_to_queue($recip['id'],NETWORK_DIASPORA,$msgtosend,false); - } + diaspora::send_profile(local_user()); } From dc2e7a66b3cf9401afbc4cf425fc5a37c66d2d74 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 14 Mar 2016 23:11:43 +0100 Subject: [PATCH 199/273] The Diaspora class is now productive --- include/diaspora.php | 5338 ++++++++++++++++++----------------------- include/diaspora2.php | 2553 -------------------- 2 files changed, 2359 insertions(+), 5532 deletions(-) delete mode 100644 include/diaspora2.php diff --git a/include/diaspora.php b/include/diaspora.php index 11fe2c9b57..75cedeccd1 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1,1702 +1,927 @@ <?php - /** * @file include/diaspora.php - * - * @todo GET /people/9aed8882b9f64896/stream + * @brief The implementation of the diaspora protocol */ -require_once('include/crypto.php'); -require_once('include/items.php'); -require_once('include/bb2diaspora.php'); -require_once('include/contact_selectors.php'); -require_once('include/queue_fn.php'); -require_once('include/lock.php'); -require_once('include/threads.php'); -require_once('mod/share.php'); -require_once('include/enotify.php'); -require_once('include/diaspora2.php'); +require_once("include/items.php"); +require_once("include/bb2diaspora.php"); +require_once("include/Scrape.php"); +require_once("include/Contact.php"); +require_once("include/Photo.php"); +require_once("include/socgraph.php"); +require_once("include/group.php"); +require_once("include/xml.php"); +require_once("include/datetime.php"); -function diaspora_dispatch_public($msg) { +/** + * @brief This class contain functions to create and send Diaspora XML files + * + */ +class diaspora { - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('mod-diaspora: disabled'); - return; - } + public static function relay_list() { - // Use a dummy importer to import the data for the public copy - $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); - $result = diaspora_dispatch($importer,$msg); - logger("Dispatcher reported ".$result, LOGGER_DEBUG); + $serverdata = get_config("system", "relay_server"); + if ($serverdata == "") + return array(); - // 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' ) - AND `account_expired` = 0 AND `account_removed` = 0 ", - dbesc(NETWORK_DIASPORA), - dbesc($msg['author']) - ); - if(count($r)) { - foreach($r as $rr) { - logger('diaspora_public: delivering to: ' . $rr['username']); - diaspora_dispatch($rr,$msg); + $relay = array(); + + $servers = explode(",", $serverdata); + + foreach($servers AS $server) { + $server = trim($server); + $batch = $server."/receive/public"; + + $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); + + if (!$relais) { + $addr = "relay@".str_replace("http://", "", normalise_link($server)); + + $r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`) + VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')", + datetime_convert(), + dbesc($addr), + dbesc($addr), + dbesc($server), + dbesc(normalise_link($server)), + dbesc($batch), + dbesc(NETWORK_DIASPORA), + intval(CONTACT_IS_FOLLOWER), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()) + ); + + $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); + if ($relais) + $relay[] = $relais[0]; + } else + $relay[] = $relais[0]; } - } - else - logger('diaspora_public: no subscribers for '.$msg["author"].' '.print_r($msg, true)); -} - - -function diaspora_dispatch($importer,$msg,$attempt=1) { - - $ret = 0; - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('mod-diaspora: disabled'); - return; + return $relay; } - $data = $msg; + function repair_signature($signature, $handle = "", $level = 1) { - // php doesn't like dashes in variable names + if ($signature == "") + return ($signature); - $msg['message'] = str_replace( - array('<activity_streams-photo>','</activity_streams-photo>'), - array('<asphoto>','</asphoto>'), - $msg['message']); + if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) { + $signature = base64_decode($signature); + logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG); - - $parsed_xml = parse_xml_string($msg['message'],false); - - $xmlbase = $parsed_xml->post; - - logger('diaspora_dispatch: ' . print_r($xmlbase,true), LOGGER_DEBUG); - - - if($xmlbase->request) { - $tempfile = tempnam(get_temppath(), "diaspora-request"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_request($importer,$xmlbase->request); - } - elseif($xmlbase->status_message) { - //$tempfile = tempnam(get_temppath(), "diaspora-status_message"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_post($importer,$xmlbase->status_message,$msg); - } - elseif($xmlbase->profile) { - //$tempfile = tempnam(get_temppath(), "diaspora-profile"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_profile($importer,$xmlbase->profile,$msg); - } - elseif($xmlbase->comment) { - //$tempfile = tempnam(get_temppath(), "diaspora-comment"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_comment($importer,$xmlbase->comment,$msg); - } - elseif($xmlbase->like) { - //$tempfile = tempnam(get_temppath(), "diaspora-like"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_like($importer,$xmlbase->like,$msg); - } - elseif($xmlbase->asphoto) { - $tempfile = tempnam(get_temppath(), "diaspora-asphoto"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg); - } - elseif($xmlbase->reshare) { - //$tempfile = tempnam(get_temppath(), "diaspora-reshare"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg); - } - elseif($xmlbase->retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-retraction"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); - } - elseif($xmlbase->signed_retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); - } - elseif($xmlbase->relayable_retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg); - } - elseif($xmlbase->photo) { - //$tempfile = tempnam(get_temppath(), "diaspora-photo"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_photo($importer,$xmlbase->photo,$msg,$attempt); - } - elseif($xmlbase->conversation) { - $tempfile = tempnam(get_temppath(), "diaspora-conversation"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_conversation($importer,$xmlbase->conversation,$msg); - } - elseif($xmlbase->message) { - $tempfile = tempnam(get_temppath(), "diaspora-message"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_message($importer,$xmlbase->message,$msg); - } - elseif($xmlbase->participation) { - //$tempfile = tempnam(get_temppath(), "diaspora-participation"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_participation($importer,$xmlbase->participation); - } - elseif($xmlbase->poll_participation) { - //$tempfile = tempnam(get_temppath(), "diaspora-poll_participation"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_participation($importer,$xmlbase->poll_participation); - } - else { - $tempfile = tempnam(get_temppath(), "diaspora-unknown"); - file_put_contents($tempfile, json_encode($data)); - logger('diaspora_dispatch: unknown message type: ' . print_r($xmlbase,true)); - } - return $ret; -} - -function diaspora_handle_from_contact($contact_id) { - $handle = False; - - logger("diaspora_handle_from_contact: contact id is " . $contact_id, LOGGER_DEBUG); - - $r = q("SELECT network, addr, self, url, nick FROM contact WHERE id = %d", - intval($contact_id) - ); - if($r) { - $contact = $r[0]; - - logger("diaspora_handle_from_contact: contact 'self' = " . $contact['self'] . " 'url' = " . $contact['url'], LOGGER_DEBUG); - - if($contact['network'] === NETWORK_DIASPORA) { - $handle = $contact['addr']; - -// logger("diaspora_handle_from_contact: contact id is a Diaspora person, handle = " . $handle, LOGGER_DEBUG); + // Do a recursive call to be able to fix even multiple levels + if ($level < 10) + $signature = self::repair_signature($signature, $handle, ++$level); } - elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { - $baseurl_start = strpos($contact['url'],'://') + 3; - $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle - $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); - $handle = $contact['nick'] . '@' . $baseurl; -// logger("diaspora_handle_from_contact: contact id is a DFRN person, handle = " . $handle, LOGGER_DEBUG); - } + return($signature); } - return $handle; -} + /** + * @brief: Decodes incoming Diaspora message + * + * @param array $importer from user table + * @param string $xml urldecoded Diaspora salmon + * + * @return array + * 'message' -> decoded Diaspora XML message + * 'author' -> author diaspora handle + * 'key' -> author public key (converted to pkcs#8) + */ + function decode($importer, $xml) { -function diaspora_get_contact_by_handle($uid,$handle) { - $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `addr` = '%s' LIMIT 1", - dbesc(NETWORK_DIASPORA), - intval($uid), - dbesc($handle) - ); - if($r && count($r)) - return $r[0]; + $public = false; + $basedom = parse_xml_string($xml); - $handle_parts = explode("@", $handle); - $nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0]; - $r = q("SELECT * FROM contact WHERE network = '%s' AND uid = %d AND nurl LIKE '%s' LIMIT 1", - dbesc(NETWORK_DFRN), - intval($uid), - dbesc($nurl_sql) - ); - if($r && count($r)) - return $r[0]; + if (!is_object($basedom)) + return false; - return false; -} + $children = $basedom->children('https://joindiaspora.com/protocol'); -function find_diaspora_person_by_handle($handle) { + if($children->header) { + $public = true; + $author_link = str_replace('acct:','',$children->header->author_id); + } else { - $person = false; - $update = false; - $got_lock = false; + $encrypted_header = json_decode(base64_decode($children->encrypted_header)); - $endlessloop = 0; - $maxloops = 10; + $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); + $ciphertext = base64_decode($encrypted_header->ciphertext); - do { - $r = q("select * from fcontact where network = '%s' and addr = '%s' limit 1", + $outer_key_bundle = ''; + openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); + + $j_outer_key_bundle = json_decode($outer_key_bundle); + + $outer_iv = base64_decode($j_outer_key_bundle->iv); + $outer_key = base64_decode($j_outer_key_bundle->key); + + $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); + + + $decrypted = pkcs5_unpad($decrypted); + + /** + * $decrypted now contains something like + * + * <decrypted_header> + * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv> + * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key> + * <author_id>galaxor@diaspora.priateship.org</author_id> + * </decrypted_header> + */ + + logger('decrypted: '.$decrypted, LOGGER_DEBUG); + $idom = parse_xml_string($decrypted,false); + + $inner_iv = base64_decode($idom->iv); + $inner_aes_key = base64_decode($idom->aes_key); + + $author_link = str_replace('acct:','',$idom->author_id); + } + + $dom = $basedom->children(NAMESPACE_SALMON_ME); + + // figure out where in the DOM tree our data is hiding + + if($dom->provenance->data) + $base = $dom->provenance; + elseif($dom->env->data) + $base = $dom->env; + elseif($dom->data) + $base = $dom; + + if (!$base) { + logger('unable to locate salmon data in xml'); + http_status_exit(400); + } + + + // Stash the signature away for now. We have to find their key or it won't be good for anything. + $signature = base64url_decode($base->sig); + + // unpack the data + + // strip whitespace so our data element will return to one big base64 blob + $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); + + + // stash away some other stuff for later + + $type = $base->data[0]->attributes()->type[0]; + $keyhash = $base->sig[0]->attributes()->keyhash[0]; + $encoding = $base->encoding; + $alg = $base->alg; + + + $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg); + + + // decode the data + $data = base64url_decode($data); + + + if($public) + $inner_decrypted = $data; + else { + + // Decode the encrypted blob + + $inner_encrypted = base64_decode($data); + $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); + $inner_decrypted = pkcs5_unpad($inner_decrypted); + } + + if (!$author_link) { + logger('Could not retrieve author URI.'); + http_status_exit(400); + } + // Once we have the author URI, go to the web and try to find their public key + // (first this will look it up locally if it is in the fcontact cache) + // This will also convert diaspora public key from pkcs#1 to pkcs#8 + + logger('Fetching key for '.$author_link); + $key = self::key($author_link); + + if (!$key) { + logger('Could not retrieve author key.'); + http_status_exit(400); + } + + $verify = rsa_verify($signed_data,$signature,$key); + + if (!$verify) { + logger('Message did not verify. Discarding.'); + http_status_exit(400); + } + + logger('Message verified.'); + + return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); + + } + + + /** + * @brief Dispatches public messages and find the fitting receivers + * + * @param array $msg The post that will be dispatched + * + * @return bool Was the message accepted? + */ + public static function dispatch_public($msg) { + + $enabled = intval(get_config("system", "diaspora_enabled")); + if (!$enabled) { + logger("diaspora is disabled"); + return false; + } + + // Use a dummy importer to import the data for the public copy + $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); + $item_id = self::dispatch($importer,$msg); + + // 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') + AND NOT `account_expired` AND NOT `account_removed`", + dbesc(NETWORK_DIASPORA), + dbesc($msg["author"]) + ); + if($r) { + foreach($r as $rr) { + logger("delivering to: ".$rr["username"]); + self::dispatch($rr,$msg); + } + } else + logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); + + return $item_id; + } + + /** + * @brief Dispatches the different message types to the different functions + * + * @param array $importer Array of the importer user + * @param array $msg The post that will be dispatched + * + * @return bool Was the message accepted? + */ + public static function dispatch($importer, $msg) { + + // 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 (!diaspora::valid_posting($msg, $fields)) { + logger("Invalid posting"); + return false; + } + + $type = $fields->getName(); + + logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG); + + switch ($type) { + case "account_deletion": + return self::receive_account_deletion($importer, $fields); + + case "comment": + return self::receive_comment($importer, $sender, $fields); + + case "conversation": + return self::receive_conversation($importer, $msg, $fields); + + case "like": + return self::receive_like($importer, $sender, $fields); + + case "message": + return self::receive_message($importer, $fields); + + case "participation": // Not implemented + return self::receive_participation($importer, $fields); + + case "photo": // Not implemented + return self::receive_photo($importer, $fields); + + case "poll_participation": // Not implemented + return self::receive_poll_participation($importer, $fields); + + case "profile": + return self::receive_profile($importer, $fields); + + case "request": + return self::receive_request($importer, $fields); + + case "reshare": + return self::receive_reshare($importer, $fields); + + case "retraction": + return self::receive_retraction($importer, $sender, $fields); + + case "status_message": + return self::receive_status_message($importer, $fields); + + default: + logger("Unknown message type ".$type); + return false; + } + + return true; + } + + /** + * @brief Checks if a posting is valid and fetches the data fields. + * + * This function does not only check the signature. + * 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? + */ + private function valid_posting($msg, &$fields) { + + $data = parse_xml_string($msg["message"], false); + + if (!is_object($data)) + return false; + + $first_child = $data->getName(); + + // Is this the new or the old version? + if ($data->getName() == "XML") { + $oldXML = true; + foreach ($data->post->children() as $child) + $element = $child; + } else { + $oldXML = false; + $element = $data; + } + + $type = $element->getName(); + $orig_type = $type; + + // All retractions are handled identically from now on. + // In the new version there will only be "retraction". + if (in_array($type, array("signed_retraction", "relayable_retraction"))) + $type = "retraction"; + + $fields = new SimpleXMLElement("<".$type."/>"); + + $signed_data = ""; + + foreach ($element->children() AS $fieldname => $entry) { + if ($oldXML) { + // Translation for the old XML structure + 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") + $fieldname = "author"; + + if ($fieldname == "recipient_handle") + $fieldname = "recipient"; + + if ($fieldname == "root_diaspora_id") + $fieldname = "root_author"; + + if ($type == "retraction") { + if ($fieldname == "post_guid") + $fieldname = "target_guid"; + + if ($fieldname == "type") + $fieldname = "target_type"; + } + } + + if ($fieldname == "author_signature") + $author_signature = base64_decode($entry); + elseif ($fieldname == "parent_author_signature") + $parent_author_signature = base64_decode($entry); + elseif ($fieldname != "target_author_signature") { + if ($signed_data != "") { + $signed_data .= ";"; + $signed_data_parent .= ";"; + } + + $signed_data .= $entry; + } + if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR + ($orig_type == "relayable_retraction")) + xml::copy($entry, $fields, $fieldname); + } + + // This is something that shouldn't happen at all. + if (in_array($type, array("status_message", "reshare", "profile"))) + if ($msg["author"] != $fields->author) { + logger("Message handle is not the same as envelope sender. Quitting this message."); + return false; + } + + // Only some message types have signatures. So we quit here for the other types. + if (!in_array($type, array("comment", "message", "like"))) + return true; + + // No author_signature? This is a must, so we quit. + if (!isset($author_signature)) + return false; + + if (isset($parent_author_signature)) { + $key = self::key($msg["author"]); + + if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) + return false; + } + + $key = self::key($fields->author); + + return rsa_verify($signed_data, $author_signature, $key, "sha256"); + } + + /** + * @brief Fetches the public key for a given handle + * + * @param string $handle The handle + * + * @return string The public key + */ + private function key($handle) { + $handle = strval($handle); + + logger("Fetching diaspora key for: ".$handle); + + $r = self::person_by_handle($handle); + if($r) + return $r["pubkey"]; + + return ""; + } + + /** + * @brief Fetches data for a given handle + * + * @param string $handle The handle + * + * @return array the queried data + */ + private function person_by_handle($handle) { + + $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", dbesc(NETWORK_DIASPORA), dbesc($handle) ); - if(count($r)) { + if ($r) { $person = $r[0]; - logger('find_diaspora_person_by handle: in cache ' . print_r($r,true), LOGGER_DEBUG); + logger("In cache ".print_r($r,true), LOGGER_DEBUG); // update record occasionally so it doesn't get stale - $d = strtotime($person['updated'] . ' +00:00'); - if($d < strtotime('now - 14 days')) + $d = strtotime($person["updated"]." +00:00"); + if ($d < strtotime("now - 14 days")) $update = true; } + if (!$person OR $update) { + logger("create or refresh", LOGGER_DEBUG); + $r = probe_url($handle, PROBE_DIASPORA); - // FETCHING PERSON INFORMATION FROM REMOTE SERVER - // - // If the person isn't in our 'fcontact' table, or if he/she is but - // his/her information hasn't been updated for more than 14 days, then - // we want to fetch the person's information from the remote server. - // - // Note that $person isn't changed by this block of code unless the - // person's information has been successfully fetched from the remote - // server. So if $person was 'false' to begin with (because he/she wasn't - // in the local cache), it'll stay false, and if $person held the local - // cache information to begin with, it'll keep that information. That way - // if there's a problem with the remote fetch, we can at least use our - // cached information--it's better than nothing. - - if((! $person) || ($update)) { - // Lock the function to prevent race conditions if multiple items - // come in at the same time from a person who doesn't exist in - // fcontact - // - // Don't loop forever. On the last loop, try to create the contact - // whether the function is locked or not. Maybe the locking thread - // has died or something. At any rate, a duplicate in 'fcontact' - // is a much smaller problem than a deadlocked thread - $got_lock = lock_function('find_diaspora_person_by_handle', false); - if(($endlessloop + 1) >= $maxloops) - $got_lock = true; - - if($got_lock) { - logger('find_diaspora_person_by_handle: create or refresh', LOGGER_DEBUG); - require_once('include/Scrape.php'); - $r = probe_url($handle, PROBE_DIASPORA); - - // Note that Friendica contacts can return a "Diaspora person" - // if Diaspora connectivity is enabled on their server - if((count($r)) && ($r['network'] === NETWORK_DIASPORA)) { - add_fcontact($r,$update); - $person = ($r); - } - - unlock_function('find_diaspora_person_by_handle'); - } - else { - logger('find_diaspora_person_by_handle: couldn\'t lock function', LOGGER_DEBUG); - if(! $person) - block_on_function_lock('find_diaspora_person_by_handle'); + // Note that Friendica contacts will return a "Diaspora person" + // if Diaspora connectivity is enabled on their server + if ($r AND ($r["network"] === NETWORK_DIASPORA)) { + self::add_fcontact($r, $update); + $person = $r; } } - } while((! $person) && (! $got_lock) && (++$endlessloop < $maxloops)); - // We need to try again if the person wasn't in 'fcontact' but the function was locked. - // The fact that the function was locked may mean that another process was creating the - // person's record. It could also mean another process was creating or updating an unrelated - // person. - // - // At any rate, we need to keep trying until we've either got the person or had a chance to - // try to fetch his/her remote information. But we don't want to block on locking the - // function, because if the other process is creating the record, then when we acquire the lock - // we'll dive right into creating another, duplicate record. We DO want to at least wait - // until the lock is released, so we don't flood the database with requests. - // - // If the person was in the 'fcontact' table, don't try again. It's not worth the time, since - // we do have some information for the person - - return $person; -} - - -function get_diaspora_key($uri) { - logger('Fetching diaspora key for: ' . $uri); - - $r = find_diaspora_person_by_handle($uri); - if($r) - return $r['pubkey']; - return ''; -} - - -function diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey) { - $a = get_app(); - - logger('diaspora_pubmsg_build: ' . $msg, LOGGER_DATA); - - - $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - -// $b64_data = base64_encode($msg); -// $b64url_data = base64url_encode($b64_data); - - $b64url_data = base64url_encode($msg); - - $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data); - - $type = 'application/xml'; - $encoding = 'base64url'; - $alg = 'RSA-SHA256'; - - $signable_data = $data . '.' . base64url_encode($type) . '.' - . base64url_encode($encoding) . '.' . base64url_encode($alg) ; - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - -$magic_env = <<< EOT -<?xml version='1.0' encoding='UTF-8'?> -<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" > - <header> - <author_id>$handle</author_id> - </header> - <me:env> - <me:encoding>base64url</me:encoding> - <me:alg>RSA-SHA256</me:alg> - <me:data type="application/xml">$data</me:data> - <me:sig>$sig</me:sig> - </me:env> -</diaspora> -EOT; - - logger('diaspora_pubmsg_build: magic_env: ' . $magic_env, LOGGER_DATA); - return $magic_env; - -} - - - - -function diaspora_msg_build($msg,$user,$contact,$prvkey,$pubkey,$public = false) { - $a = get_app(); - - if($public) - return diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey); - - logger('diaspora_msg_build: ' . $msg, LOGGER_DATA); - - // without a public key nothing will work - - if(! $pubkey) { - logger('diaspora_msg_build: pubkey missing: contact id: ' . $contact['id']); - return ''; + return $person; } - $inner_aes_key = random_string(32); - $b_inner_aes_key = base64_encode($inner_aes_key); - $inner_iv = random_string(16); - $b_inner_iv = base64_encode($inner_iv); + /** + * @brief Updates the fcontact table + * + * @param array $arr The fcontact data + * @param bool $update Update or insert? + * + * @return string The id of the fcontact entry + */ + private function add_fcontact($arr, $update = false) { + /// @todo Remove this function from include/network.php - $outer_aes_key = random_string(32); - $b_outer_aes_key = base64_encode($outer_aes_key); - $outer_iv = random_string(16); - $b_outer_iv = base64_encode($outer_iv); - - $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - $padded_data = pkcs5_pad($msg,16); - $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); - - $b64_data = base64_encode($inner_encrypted); - - - $b64url_data = base64url_encode($b64_data); - $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data); - - $type = 'application/xml'; - $encoding = 'base64url'; - $alg = 'RSA-SHA256'; - - $signable_data = $data . '.' . base64url_encode($type) . '.' - . base64url_encode($encoding) . '.' . base64url_encode($alg) ; - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - -$decrypted_header = <<< EOT -<decrypted_header> - <iv>$b_inner_iv</iv> - <aes_key>$b_inner_aes_key</aes_key> - <author_id>$handle</author_id> -</decrypted_header> -EOT; - - $decrypted_header = pkcs5_pad($decrypted_header,16); - - $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); - - $outer_json = json_encode(array('iv' => $b_outer_iv,'key' => $b_outer_aes_key)); - - $encrypted_outer_key_bundle = ''; - openssl_public_encrypt($outer_json,$encrypted_outer_key_bundle,$pubkey); - - $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); - - logger('outer_bundle: ' . $b64_encrypted_outer_key_bundle . ' key: ' . $pubkey, LOGGER_DATA); - - $encrypted_header_json_object = json_encode(array('aes_key' => base64_encode($encrypted_outer_key_bundle), - 'ciphertext' => base64_encode($ciphertext))); - $cipher_json = base64_encode($encrypted_header_json_object); - - $encrypted_header = '<encrypted_header>' . $cipher_json . '</encrypted_header>'; - -$magic_env = <<< EOT -<?xml version='1.0' encoding='UTF-8'?> -<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" > - $encrypted_header - <me:env> - <me:encoding>base64url</me:encoding> - <me:alg>RSA-SHA256</me:alg> - <me:data type="application/xml">$data</me:data> - <me:sig>$sig</me:sig> - </me:env> -</diaspora> -EOT; - - logger('diaspora_msg_build: magic_env: ' . $magic_env, LOGGER_DATA); - return $magic_env; - -} - -/** - * - * diaspora_decode($importer,$xml) - * array $importer -> from user table - * string $xml -> urldecoded Diaspora salmon - * - * Returns array - * 'message' -> decoded Diaspora XML message - * 'author' -> author diaspora handle - * 'key' -> author public key (converted to pkcs#8) - * - * Author and key are used elsewhere to save a lookup for verifying replies and likes - */ - - -function diaspora_decode($importer,$xml) { - - $tempfile = tempnam(get_temppath(), "diaspora-decode"); - file_put_contents($tempfile, json_encode(array("importer" => $importer, "xml" => $xml))); - - $public = false; - $basedom = parse_xml_string($xml); - - $children = $basedom->children('https://joindiaspora.com/protocol'); - - if($children->header) { - $public = true; - $author_link = str_replace('acct:','',$children->header->author_id); - } - else { - - $encrypted_header = json_decode(base64_decode($children->encrypted_header)); - - $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); - $ciphertext = base64_decode($encrypted_header->ciphertext); - - $outer_key_bundle = ''; - openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); - - $j_outer_key_bundle = json_decode($outer_key_bundle); - - $outer_iv = base64_decode($j_outer_key_bundle->iv); - $outer_key = base64_decode($j_outer_key_bundle->key); - - $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); - - - $decrypted = pkcs5_unpad($decrypted); - - /** - * $decrypted now contains something like - * - * <decrypted_header> - * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv> - * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key> - -***** OBSOLETE - - * <author> - * <name>Ryan Hughes</name> - * <uri>acct:galaxor@diaspora.pirateship.org</uri> - * </author> - -***** CURRENT - - * <author_id>galaxor@diaspora.priateship.org</author_id> - -***** END DIFFS - - * </decrypted_header> - */ - - logger('decrypted: ' . $decrypted, LOGGER_DEBUG); - $idom = parse_xml_string($decrypted,false); - - $inner_iv = base64_decode($idom->iv); - $inner_aes_key = base64_decode($idom->aes_key); - - $author_link = str_replace('acct:','',$idom->author_id); + if($update) { + $r = q("UPDATE `fcontact` SET + `name` = '%s', + `photo` = '%s', + `request` = '%s', + `nick` = '%s', + `addr` = '%s', + `batch` = '%s', + `notify` = '%s', + `poll` = '%s', + `confirm` = '%s', + `alias` = '%s', + `pubkey` = '%s', + `updated` = '%s' + WHERE `url` = '%s' AND `network` = '%s'", + dbesc($arr["name"]), + dbesc($arr["photo"]), + dbesc($arr["request"]), + dbesc($arr["nick"]), + dbesc($arr["addr"]), + dbesc($arr["batch"]), + dbesc($arr["notify"]), + dbesc($arr["poll"]), + dbesc($arr["confirm"]), + dbesc($arr["alias"]), + dbesc($arr["pubkey"]), + dbesc(datetime_convert()), + dbesc($arr["url"]), + dbesc($arr["network"]) + ); + } else { + $r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`, + `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`) + VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", + dbesc($arr["url"]), + dbesc($arr["name"]), + dbesc($arr["photo"]), + dbesc($arr["request"]), + dbesc($arr["nick"]), + dbesc($arr["addr"]), + dbesc($arr["batch"]), + dbesc($arr["notify"]), + dbesc($arr["poll"]), + dbesc($arr["confirm"]), + dbesc($arr["network"]), + dbesc($arr["alias"]), + dbesc($arr["pubkey"]), + dbesc(datetime_convert()) + ); + } + return $r; } - $dom = $basedom->children(NAMESPACE_SALMON_ME); + public static function handle_from_contact($contact_id) { + $handle = False; - // figure out where in the DOM tree our data is hiding + logger("contact id is ".$contact_id, LOGGER_DEBUG); - if($dom->provenance->data) - $base = $dom->provenance; - elseif($dom->env->data) - $base = $dom->env; - elseif($dom->data) - $base = $dom; + $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", + intval($contact_id) + ); + if($r) { + $contact = $r[0]; - if(! $base) { - logger('mod-diaspora: unable to locate salmon data in xml '); - http_status_exit(400); + logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); + + if($contact['addr'] != "") + $handle = $contact['addr']; + elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { + $baseurl_start = strpos($contact['url'],'://') + 3; + $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle + $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); + $handle = $contact['nick'].'@'.$baseurl; + } + } + + return $handle; } + private function contact_by_handle($uid, $handle) { + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", + intval($uid), + dbesc($handle) + ); - // Stash the signature away for now. We have to find their key or it won't be good for anything. - $signature = base64url_decode($base->sig); + if ($r) + return $r[0]; - // unpack the data + $handle_parts = explode("@", $handle); + $nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0]; + $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1", + dbesc(NETWORK_DFRN), + intval($uid), + dbesc($nurl_sql) + ); + if($r) + return $r[0]; - // strip whitespace so our data element will return to one big base64 blob - $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); - - - // stash away some other stuff for later - - $type = $base->data[0]->attributes()->type[0]; - $keyhash = $base->sig[0]->attributes()->keyhash[0]; - $encoding = $base->encoding; - $alg = $base->alg; - - - $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg); - - - // decode the data - $data = base64url_decode($data); - - - if($public) { - $inner_decrypted = $data; - } - else { - - // Decode the encrypted blob - - $inner_encrypted = base64_decode($data); - $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); - $inner_decrypted = pkcs5_unpad($inner_decrypted); + return false; } - if(! $author_link) { - logger('mod-diaspora: Could not retrieve author URI.'); - http_status_exit(400); - } - - // Once we have the author URI, go to the web and try to find their public key - // (first this will look it up locally if it is in the fcontact cache) - // This will also convert diaspora public key from pkcs#1 to pkcs#8 - - logger('mod-diaspora: Fetching key for ' . $author_link ); - $key = get_diaspora_key($author_link); - - if(! $key) { - logger('mod-diaspora: Could not retrieve author key.'); - http_status_exit(400); - } - - $verify = rsa_verify($signed_data,$signature,$key); - - if(! $verify) { - logger('mod-diaspora: Message did not verify. Discarding.'); - http_status_exit(400); - } - - logger('mod-diaspora: Message verified.'); - - return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); - -} - - -function diaspora_request($importer,$xml) { - - $a = get_app(); - - $sender_handle = unxmlify($xml->sender_handle); - $recipient_handle = unxmlify($xml->recipient_handle); - - if(! $sender_handle || ! $recipient_handle) - return; - - $contact = diaspora_get_contact_by_handle($importer['uid'],$sender_handle); - - if($contact) { + private function post_allow($importer, $contact, $is_comment = false) { // perhaps we were already sharing with this person. Now they're sharing with us. // That makes us friends. - - if($contact['rel'] == CONTACT_IS_FOLLOWER && in_array($importer['page-flags'], array(PAGE_FREELOVE))) { + // Normally this should have handled by getting a request - but this could get lost + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", intval(CONTACT_IS_FRIEND), - intval($contact['id']), - intval($importer['uid']) + intval($contact["id"]), + intval($importer["uid"]) ); - } - // send notification - - $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", - intval($importer['uid']) - ); - - if((count($r)) && (!$r[0]['hide-friends']) && (!$contact['hidden']) && intval(get_pconfig($importer['uid'],'system','post_newfriend'))) { - require_once('include/items.php'); - - $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", - intval($importer['uid']) - ); - - // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array - - if(count($self) && $contact['rel'] == CONTACT_IS_FOLLOWER) { - - $arr = array(); - $arr['uri'] = $arr['parent-uri'] = item_new_uri($a->get_hostname(), $importer['uid']); - $arr['uid'] = $importer['uid']; - $arr['contact-id'] = $self[0]['id']; - $arr['wall'] = 1; - $arr['type'] = 'wall'; - $arr['gravity'] = 0; - $arr['origin'] = 1; - $arr['author-name'] = $arr['owner-name'] = $self[0]['name']; - $arr['author-link'] = $arr['owner-link'] = $self[0]['url']; - $arr['author-avatar'] = $arr['owner-avatar'] = $self[0]['thumb']; - $arr['verb'] = ACTIVITY_FRIEND; - $arr['object-type'] = ACTIVITY_OBJ_PERSON; - - $A = '[url=' . $self[0]['url'] . ']' . $self[0]['name'] . '[/url]'; - $B = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; - $BPhoto = '[url=' . $contact['url'] . ']' . '[img]' . $contact['thumb'] . '[/img][/url]'; - $arr['body'] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto; - - $arr['object'] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $contact['name'] . '</title>' - . '<id>' . $contact['url'] . '/' . $contact['name'] . '</id>'; - $arr['object'] .= '<link>' . xmlify('<link rel="alternate" type="text/html" href="' . $contact['url'] . '" />' . "\n"); - $arr['object'] .= xmlify('<link rel="photo" type="image/jpeg" href="' . $contact['thumb'] . '" />' . "\n"); - $arr['object'] .= '</link></object>' . "\n"; - $arr['last-child'] = 1; - - $arr['allow_cid'] = $user[0]['allow_cid']; - $arr['allow_gid'] = $user[0]['allow_gid']; - $arr['deny_cid'] = $user[0]['deny_cid']; - $arr['deny_gid'] = $user[0]['deny_gid']; - - $i = item_store($arr); - if($i) - proc_run('php',"include/notifier.php","activity","$i"); - - } - + $contact["rel"] = CONTACT_IS_FRIEND; + logger("defining user ".$contact["nick"]." as friend"); } - return; - } + if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"])) + return false; + if($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; - $ret = find_diaspora_person_by_handle($sender_handle); - - - if((! count($ret)) || ($ret['network'] != NETWORK_DIASPORA)) { - logger('diaspora_request: Cannot resolve diaspora handle ' . $sender_handle . ' for ' . $recipient_handle); - return; - } - - $batch = (($ret['batch']) ? $ret['batch'] : implode('/', array_slice(explode('/',$ret['url']),0,3)) . '/receive/public'); - - - - $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) - VALUES ( %d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d) ", - intval($importer['uid']), - dbesc($ret['network']), - dbesc($ret['addr']), - datetime_convert(), - dbesc($ret['url']), - dbesc(normalise_link($ret['url'])), - dbesc($batch), - dbesc($ret['name']), - dbesc($ret['nick']), - dbesc($ret['photo']), - dbesc($ret['pubkey']), - dbesc($ret['notify']), - dbesc($ret['poll']), - 1, - 2 - ); - - // find the contact record we just created - - $contact_record = diaspora_get_contact_by_handle($importer['uid'],$sender_handle); - - if(! $contact_record) { - logger('diaspora_request: unable to locate newly created contact record.'); - return; - } - - $def_gid = get_default_group($importer['uid'], $ret["network"]); - if (intval($def_gid)) { - require_once('include/group.php'); - group_add_member($importer['uid'], '', $contact_record['id'], $def_gid); - } - - if($importer['page-flags'] == PAGE_NORMAL) { - - $hash = random_string() . (string) time(); // Generate a confirm_key - - $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime` ) - VALUES ( %d, %d, %d, %d, '%s', '%s', '%s' )", - intval($importer['uid']), - intval($contact_record['id']), - 0, - 0, - dbesc( t('Sharing notification from Diaspora network')), - dbesc($hash), - dbesc(datetime_convert()) - ); - } - else { - - // automatic friend approval - - require_once('include/Photo.php'); - - update_contact_avatar($contact_record['photo'],$importer['uid'],$contact_record['id']); - - // technically they are sharing with us (CONTACT_IS_SHARING), - // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX - // we are going to change the relationship and make them a follower. - - if($importer['page-flags'] == PAGE_FREELOVE) - $new_relation = CONTACT_IS_FRIEND; - else - $new_relation = CONTACT_IS_FOLLOWER; - - $r = q("UPDATE `contact` SET `rel` = %d, - `name-date` = '%s', - `uri-date` = '%s', - `blocked` = 0, - `pending` = 0, - `writable` = 1 - WHERE `id` = %d - ", - intval($new_relation), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($contact_record['id']) - ); - - $u = q("select * from user where uid = %d limit 1",intval($importer['uid'])); - if($u) - $ret = diaspora_share($u[0],$contact_record); - } - - return; -} - -function diaspora_post_allow($importer,$contact, $is_comment = false) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - // Normally this should have handled by getting a request - but this could get lost - if($contact['rel'] == CONTACT_IS_FOLLOWER && in_array($importer['page-flags'], array(PAGE_FREELOVE))) { - q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", - intval(CONTACT_IS_FRIEND), - intval($contact['id']), - intval($importer['uid']) - ); - $contact['rel'] = CONTACT_IS_FRIEND; - logger('diaspora_post_allow: defining user '.$contact["nick"].' as friend'); - } - - if(($contact['blocked']) || ($contact['readonly']) || ($contact['archive'])) - return false; - if($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) + // Messages for the global users are always accepted + if ($importer["uid"] == 0) return true; - // Messages for the global users are always accepted - if ($importer['uid'] == 0) - return true; - - return false; -} - -function diaspora_is_redmatrix($url) { - return(strstr($url, "/channel/")); -} - -function diaspora_plink($addr, $guid) { - $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); - - // Fallback - if (!$r) - return 'https://'.substr($addr,strpos($addr,'@')+1).'/posts/'.$guid; - - // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table - // So we try another way as well. - $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); - if ($s) - $r[0]["network"] = $s[0]["network"]; - - if ($r[0]["network"] == NETWORK_DFRN) - return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); - - if (diaspora_is_redmatrix($r[0]["url"])) - return $r[0]["url"]."/?f=&mid=".$guid; - - return 'https://'.substr($addr,strpos($addr,'@')+1).'/posts/'.$guid; -} - -function diaspora_repair_signature($signature, $handle = "", $level = 1) { - - if ($signature == "") - return($signature); - - if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) { - $signature = base64_decode($signature); - logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG); - - // Do a recursive call to be able to fix even multiple levels - if ($level < 10) - $signature = diaspora_repair_signature($signature, $handle, ++$level); - } - - return($signature); -} - -function diaspora_post($importer,$xml,$msg) { - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) { - logger('diaspora_post: A Contact for handle '.$diaspora_handle.' and user '.$importer['uid'].' was not found'); - return 203; - } - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_post: Ignoring this author.'); - return 202; - } - - $message_id = $diaspora_handle . ':' . $guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_post: message exists: ' . $guid); - return 208; - } - - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); - - $body = diaspora2bb($xml->raw_message); - - $datarray = array(); - - $datarray["object"] = json_encode($xml); - - if($xml->photo->remote_photo_path AND $xml->photo->remote_photo_name) - $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; - else { - $datarray['object-type'] = ACTIVITY_OBJ_NOTE; - // Add OEmbed and other information to the body - if (!diaspora_is_redmatrix($contact['url'])) - $body = add_page_info_to_body($body, false, true); - } - - $str_tags = ''; - - $cnt = preg_match_all('/@\[url=(.*?)\[\/url\]/ism',$body,$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - if(strlen($str_tags)) - $str_tags .= ','; - $str_tags .= '@[url=' . $mtch[1] . '[/url]'; - } - } - - $plink = diaspora_plink($diaspora_handle, $guid); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['verb'] = ACTIVITY_POST; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = $plink; - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - //$datarray['owner-avatar'] = $contact['thumb']; - $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']); - $datarray['author-name'] = $contact['name']; - $datarray['author-link'] = $contact['url']; - $datarray['author-avatar'] = $contact['thumb']; - $datarray['body'] = $body; - $datarray['tag'] = $str_tags; - if ($xml->provider_display_name) - $datarray["app"] = unxmlify($xml->provider_display_name); - else - $datarray['app'] = 'Diaspora'; - - // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. - - $datarray['visible'] = ((strlen($body)) ? 1 : 0); - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - logger("Stored item with message id ".$message_id, LOGGER_DEBUG); - - return 201; - -} - -function DiasporaFetchGuid($item) { - preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", - function ($match) use ($item){ - return(DiasporaFetchGuidSub($match, $item)); - },$item["body"]); -} - -function DiasporaFetchGuidSub($match, $item) { - $a = get_app(); - - if (!diaspora_store_by_guid($match[1], $item["author-link"])) - diaspora_store_by_guid($match[1], $item["owner-link"]); -} - -function diaspora_store_by_guid($guid, $server, $uid = 0) { - require_once("include/Contact.php"); - - $serverparts = parse_url($server); - $server = $serverparts["scheme"]."://".$serverparts["host"]; - - logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); - - $item = diaspora_fetch_message($guid, $server); - - if (!$item) return false; + } - logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); - - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $created = $item["created"]; - $author = $item["author"]; - $guid = $item["guid"]; - $private = $item["private"]; - $object = $item["object"]; - $objecttype = $item["object-type"]; - - $message_id = $author.':'.$guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), - dbesc($guid) - ); - if(count($r)) - return $r[0]["id"]; - - $person = find_diaspora_person_by_handle($author); - - $contact_id = get_contact($person['url'], $uid); - - $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id)); - $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid)); - - if ($contacts AND $importers) - if(!diaspora_post_allow($importers[0],$contacts[0], false)) { - logger('Ignoring author '.$person['url'].' for uid '.$uid); + private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { + $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; - } else - logger('Author '.$person['url'].' is allowed for uid '.$uid); + } - $datarray = array(); - $datarray['uid'] = $uid; - $datarray['contact-id'] = $contact_id; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = diaspora_plink($author, $guid); - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['owner-name'] = $datarray['author-name']; - $datarray['owner-link'] = $datarray['author-link']; - $datarray['owner-avatar'] = $datarray['author-avatar']; - $datarray['body'] = $body; - $datarray['tag'] = $str_tags; - $datarray['app'] = $app; - $datarray['visible'] = ((strlen($body)) ? 1 : 0); - $datarray['object'] = $object; - $datarray['object-type'] = $objecttype; + if (!self::post_allow($importer, $contact, $is_comment)) { + logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]); + return false; + } + return $contact; + } - if ($datarray['contact-id'] == 0) - return false; + private function message_exists($uid, $guid) { + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), + dbesc($guid) + ); - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); + if($r) { + logger("message ".$guid." already exists for user ".$uid); + return true; + } - /// @TODO - /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post - - return $message_id; -} - -function diaspora_fetch_message($guid, $server, $level = 0) { - - if ($level > 5) - return false; - - $a = get_app(); - - // This will not work if the server is not a Diaspora server - $source_url = $server.'/p/'.$guid.'.xml'; - $x = fetch_url($source_url); - if(!$x) - return false; - - $x = str_replace(array('<activity_streams-photo>','</activity_streams-photo>'),array('<asphoto>','</asphoto>'),$x); - $source_xml = parse_xml_string($x,false); - - $item = array(); - $item["app"] = 'Diaspora'; - $item["guid"] = $guid; - $body = ""; - - if ($source_xml->post->status_message->created_at) - $item["created"] = unxmlify($source_xml->post->status_message->created_at); - - if ($source_xml->post->status_message->provider_display_name) - $item["app"] = unxmlify($source_xml->post->status_message->provider_display_name); - - if ($source_xml->post->status_message->diaspora_handle) - $item["author"] = unxmlify($source_xml->post->status_message->diaspora_handle); - - if ($source_xml->post->status_message->guid) - $item["guid"] = unxmlify($source_xml->post->status_message->guid); - - $item["private"] = (unxmlify($source_xml->post->status_message->public) == 'false'); - $item["object"] = json_encode($source_xml->post); - - if(strlen($source_xml->post->asphoto->objectId) && ($source_xml->post->asphoto->objectId != 0) && ($source_xml->post->asphoto->image_url)) { - $item["object-type"] = ACTIVITY_OBJ_PHOTO; - $body = '[url=' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '][img]' . notags(unxmlify($source_xml->post->asphoto->objectId)) . '[/img][/url]' . "\n"; - $body = scale_external_images($body,false); - } elseif($source_xml->post->asphoto->image_url) { - $item["object-type"] = ACTIVITY_OBJ_PHOTO; - $body = '[img]' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '[/img]' . "\n"; - $body = scale_external_images($body); - } elseif($source_xml->post->status_message) { - $body = diaspora2bb($source_xml->post->status_message->raw_message); - - // Checking for embedded pictures - if($source_xml->post->status_message->photo->remote_photo_path AND - $source_xml->post->status_message->photo->remote_photo_name) { - - $item["object-type"] = ACTIVITY_OBJ_PHOTO; - - $remote_photo_path = notags(unxmlify($source_xml->post->status_message->photo->remote_photo_path)); - $remote_photo_name = notags(unxmlify($source_xml->post->status_message->photo->remote_photo_name)); - - $body = '[img]'.$remote_photo_path.$remote_photo_name.'[/img]'."\n".$body; - - logger('embedded picture link found: '.$body, LOGGER_DEBUG); - } else - $item["object-type"] = ACTIVITY_OBJ_NOTE; - - $body = scale_external_images($body); - - // Add OEmbed and other information to the body - /// @TODO It could be a repeated redmatrix item - /// Then we shouldn't add further data to it - if ($item["object-type"] == ACTIVITY_OBJ_NOTE) - $body = add_page_info_to_body($body, false, true); - - } elseif($source_xml->post->reshare) { - // Reshare of a reshare - return diaspora_fetch_message($source_xml->post->reshare->root_guid, $server, ++$level); - } else { - // Maybe it is a reshare of a photo that will be delivered at a later time (testing) - logger('no content found: '.print_r($source_xml,true)); return false; } - if (trim($body) == "") - return false; - - $item["tag"] = ''; - $item["body"] = $body; - - return $item; -} - -function diaspora_reshare($importer,$xml,$msg) { - - logger('diaspora_reshare: init: ' . print_r($xml,true)); - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; + private function fetch_guid($item) { + preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", + function ($match) use ($item){ + return(self::fetch_guid_sub($match, $item)); + },$item["body"]); } - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_reshare: Ignoring this author: ' . $diaspora_handle . ' ' . print_r($xml,true)); - return 202; + private function fetch_guid_sub($match, $item) { + if (!self::store_by_guid($match[1], $item["author-link"])) + self::store_by_guid($match[1], $item["owner-link"]); } - $message_id = $diaspora_handle . ':' . $guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_reshare: message exists: ' . $guid); - return; + private function store_by_guid($guid, $server, $uid = 0) { + $serverparts = parse_url($server); + $server = $serverparts["scheme"]."://".$serverparts["host"]; + + logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); + + $msg = self::message($guid, $server); + + if (!$msg) + return false; + + logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); + + // Now call the dispatcher + return self::dispatch_public($msg); } - $orig_author = notags(unxmlify($xml->root_diaspora_id)); - $orig_guid = notags(unxmlify($xml->root_guid)); - $orig_url = $a->get_baseurl()."/display/".$orig_guid; + private function message($guid, $server, $level = 0) { - $create_original_post = false; + if ($level > 5) + return false; - // Do we already have this item? - $r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", - dbesc($orig_guid), - dbesc(NETWORK_DIASPORA) - ); - if(count($r)) { - logger('reshared message '.$orig_guid." reshared by ".$guid.' already exists on system.'); + // This will work for Diaspora and newer Friendica servers + $source_url = $server."/p/".$guid.".xml"; + $x = fetch_url($source_url); + if(!$x) + return false; - // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - require_once('include/api.php'); - if (api_share_as_retweet($r[0])) - $r = array(); - else { - $body = $r[0]["body"]; - $str_tags = $r[0]["tag"]; - $app = $r[0]["app"]; - $orig_created = $r[0]["created"]; - $orig_plink = $r[0]["plink"]; - $orig_uri = $r[0]["uri"]; - $object = $r[0]["object"]; - $objecttype = $r[0]["object-type"]; + $source_xml = parse_xml_string($x, false); + + if (!is_object($source_xml)) + return false; + + if ($source_xml->post->reshare) { + // Reshare of a reshare - old Diaspora version + return self::message($source_xml->post->reshare->root_guid, $server, ++$level); + } elseif ($source_xml->getName() == "reshare") { + // Reshare of a reshare - new Diaspora version + return self::message($source_xml->root_guid, $server, ++$level); + } + + $author = ""; + + // Fetch the author - for the old and the new Diaspora version + if ($source_xml->post->status_message->diaspora_handle) + $author = (string)$source_xml->post->status_message->diaspora_handle; + elseif ($source_xml->author AND ($source_xml->getName() == "status_message")) + $author = (string)$source_xml->author; + + // If this isn't a "status_message" then quit + if (!$author) + return false; + + $msg = array("message" => $x, "author" => $author); + + $msg["key"] = self::key($msg["author"]); + + return $msg; + } + + private function parent_item($uid, $guid, $author, $contact) { + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, + `author-name`, `author-link`, `author-avatar`, + `owner-name`, `owner-link`, `owner-avatar` + FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), dbesc($guid)); + + if(!$r) { + $result = self::store_by_guid($guid, $contact["url"], $uid); + + if (!$result) { + $person = self::person_by_handle($author); + $result = self::store_by_guid($guid, $person["url"], $uid); + } + + if ($result) { + logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG); + + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, + `author-name`, `author-link`, `author-avatar`, + `owner-name`, `owner-link`, `owner-avatar` + FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), dbesc($guid)); + } + } + + if (!$r) { + logger("parent item not found: parent: ".$guid." - user: ".$uid); + return false; + } else { + logger("parent item found: parent: ".$guid." - user: ".$uid); + return $r[0]; } } - if (!count($r)) { - $body = ""; - $str_tags = ""; - $app = ""; + private function author_contact_by_url($contact, $person, $uid) { - $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = diaspora_fetch_message($orig_guid, $server); - - if (!$item) { - $server = 'https://'.substr($diaspora_handle,strpos($diaspora_handle,'@')+1); - logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($diaspora_handle,strpos($diaspora_handle,'@')+1); - logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); + $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc(normalise_link($person["url"])), intval($uid)); + if ($r) { + $cid = $r[0]["id"]; + $network = $r[0]["network"]; + } else { + $cid = $contact["id"]; + $network = NETWORK_DIASPORA; } - if ($item) { - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $orig_created = $item["created"]; - $orig_author = $item["author"]; - $orig_guid = $item["guid"]; - $orig_plink = diaspora_plink($orig_author, $orig_guid); - $orig_uri = $orig_author.':'.$orig_guid; - $create_original_post = ($body != ""); - $object = $item["object"]; - $objecttype = $item["object-type"]; - } + return (array("cid" => $cid, "network" => $network)); } - $plink = diaspora_plink($diaspora_handle, $guid); - - $person = find_diaspora_person_by_handle($orig_author); - - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); - - $datarray = array(); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = $plink; - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']); - if (!intval(get_config('system','wall-to-wall_share'))) { - $prefix = share_header($person['name'], $person['url'], ((x($person,'thumb')) ? $person['thumb'] : $person['photo']), $orig_guid, $orig_created, $orig_url); - - $datarray['author-name'] = $contact['name']; - $datarray['author-link'] = $contact['url']; - $datarray['author-avatar'] = $contact['thumb']; - $datarray['body'] = $prefix.$body."[/share]"; - } else { - // Let reshared messages look like wall-to-wall posts - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['body'] = $body; + public static function is_redmatrix($url) { + return(strstr($url, "/channel/")); } - $datarray["object"] = json_encode($xml); - $datarray['object-type'] = $objecttype; + private function plink($addr, $guid) { + $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); - $datarray['tag'] = $str_tags; - $datarray['app'] = $app; + // Fallback + if (!$r) + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; - // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. (testing) - $datarray['visible'] = ((strlen($body)) ? 1 : 0); + // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table + // So we try another way as well. + $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); + if ($s) + $r[0]["network"] = $s[0]["network"]; - // Store the original item of a reshare - if ($create_original_post) { - require_once("include/Contact.php"); + if ($r[0]["network"] == NETWORK_DFRN) + return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); - $datarray2 = $datarray; + if (self::is_redmatrix($r[0]["url"])) + return $r[0]["url"]."/?f=&mid=".$guid; - $datarray2['uid'] = 0; - $datarray2['contact-id'] = get_contact($person['url'], 0); - $datarray2['guid'] = $orig_guid; - $datarray2['uri'] = $datarray2['parent-uri'] = $orig_uri; - $datarray2['changed'] = $datarray2['created'] = $datarray2['edited'] = $datarray2['commented'] = $datarray2['received'] = datetime_convert('UTC','UTC',$orig_created); - $datarray2['parent'] = 0; - $datarray2['plink'] = $orig_plink; - - $datarray2['author-name'] = $person['name']; - $datarray2['author-link'] = $person['url']; - $datarray2['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray2['owner-name'] = $datarray2['author-name']; - $datarray2['owner-link'] = $datarray2['author-link']; - $datarray2['owner-avatar'] = $datarray2['author-avatar']; - $datarray2['body'] = $body; - $datarray2["object"] = $object; - - DiasporaFetchGuid($datarray2); - $message_id = item_store($datarray2); - - logger("Store original item ".$orig_guid." under message id ".$message_id); + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; } - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); + private function receive_account_deletion($importer, $data) { + $author = notags(unxmlify($data->author)); - return; - -} - - -function diaspora_asphoto($importer,$xml,$msg) { - logger('diaspora_asphoto called'); - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_asphoto: Ignoring this author.'); - return 202; - } - - $message_id = $diaspora_handle . ':' . $guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_asphoto: message exists: ' . $guid); - return; - } - - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); - - if(strlen($xml->objectId) && ($xml->objectId != 0) && ($xml->image_url)) { - $body = '[url=' . notags(unxmlify($xml->image_url)) . '][img]' . notags(unxmlify($xml->objectId)) . '[/img][/url]' . "\n"; - $body = scale_external_images($body,false); - } - elseif($xml->image_url) { - $body = '[img]' . notags(unxmlify($xml->image_url)) . '[/img]' . "\n"; - $body = scale_external_images($body); - } - else { - logger('diaspora_asphoto: no photo url found.'); - return; - } - - $plink = diaspora_plink($diaspora_handle, $guid); - - $datarray = array(); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = $plink; - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - //$datarray['owner-avatar'] = $contact['thumb']; - $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']); - $datarray['author-name'] = $contact['name']; - $datarray['author-link'] = $contact['url']; - $datarray['author-avatar'] = $contact['thumb']; - $datarray['body'] = $body; - $datarray["object"] = json_encode($xml); - $datarray['object-type'] = ACTIVITY_OBJ_PHOTO; - - $datarray['app'] = 'Diaspora/Cubbi.es'; - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - //if($message_id) { - // q("update item set plink = '%s' where id = %d", - // dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), - // intval($message_id) - // ); - //} - - return; - -} - -function diaspora_comment($importer,$xml,$msg) { - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $parent_guid = notags(unxmlify($xml->parent_guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $target_type = notags(unxmlify($xml->target_type)); - $text = unxmlify($xml->text); - $author_signature = notags(unxmlify($xml->author_signature)); - - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_comment: cannot find contact: ' . $msg['author']); - return; - } - - if(! diaspora_post_allow($importer,$contact, true)) { - logger('diaspora_comment: Ignoring this author.'); - return 202; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid); - return; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - - if(!count($r)) { - $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); - - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); + $contact = self::contact_by_handle($importer["uid"], $author); + if (!$contact) { + logger("cannot find contact for author: ".$author); + return false; } - if ($result) { - logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG); + // We now remove the contact + contact_remove($contact["id"]); + return true; + } - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) + private function receive_comment($importer, $sender, $data) { + $guid = notags(unxmlify($data->guid)); + $parent_guid = notags(unxmlify($data->parent_guid)); + $text = unxmlify($data->text); + $author = notags(unxmlify($data->author)); + + $contact = self::allowed_contact_by_handle($importer, $sender, true); + if (!$contact) + return false; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; + + $person = self::person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author details"); + return false; + } + + // Fetch the contact id - if we know this contact + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; + + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); + + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; + + $datarray["type"] = "remote-comment"; + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_COMMENT; + $datarray["parent-uri"] = $parent_item["uri"]; + + $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; + $datarray["object"] = json_encode($data); + + $datarray["body"] = diaspora2bb($text); + + self::fetch_guid($datarray); + + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // We now store the raw data so that we are more flexible. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($message_id), + dbesc(json_encode($data)) ); + + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); } + + return $message_id; } - if(! count($r)) { - logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid); - return; - } - $parent_item = $r[0]; - - - /* How Diaspora performs comment signature checking: - - - If an item has been sent by the comment author to the top-level post owner to relay on - to the rest of the contacts on the top-level post, the top-level post owner should check - the author_signature, then create a parent_author_signature before relaying the comment on - - If an item has been relayed on by the top-level post owner, the contacts who receive it - check only the parent_author_signature. Basically, they trust that the top-level post - owner has already verified the authenticity of anything he/she sends out - - In either case, the signature that get checked is the signature created by the person - who sent the salmon - */ - - $signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle; - $key = $msg['key']; - - if($parent_author_signature) { - // If a parent_author_signature exists, then we've received the comment - // relayed from the top-level post owner. There's no need to check the - // author_signature if the parent_author_signature is valid - - $parent_author_signature = base64_decode($parent_author_signature); - - if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_comment: top-level owner verification failed.'); - return; - } - } - else { - // If there's no parent_author_signature, then we've received the comment - // from the comment creator. In that case, the person is commenting on - // our post, so he/she must be a contact of ours and his/her public key - // should be in $msg['key'] - - $author_signature = base64_decode($author_signature); - - if(! rsa_verify($signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_comment: comment author verification failed.'); - return; - } - } - - // Phew! Everything checks out. Now create an item. - - // Find the original comment author information. - // We need this to make sure we display the comment author - // information (name and avatar) correctly. - if(strcasecmp($diaspora_handle,$msg['author']) == 0) - $person = $contact; - else { - $person = find_diaspora_person_by_handle($diaspora_handle); - - if(! is_array($person)) { - logger('diaspora_comment: unable to find author details'); - return; - } - } - - // Fetch the contact id - if we know this contact - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person['url'])), intval($importer['uid'])); - if ($r) { - $cid = $r[0]['id']; - $network = $r[0]['network']; - } else { - $cid = $contact['id']; - $network = NETWORK_DIASPORA; - } - - $body = diaspora2bb($text); - $message_id = $diaspora_handle . ':' . $guid; - - $datarray = array(); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $cid; - $datarray['type'] = 'remote-comment'; - $datarray['wall'] = $parent_item['wall']; - $datarray['network'] = $network; - $datarray['verb'] = ACTIVITY_POST; - $datarray['gravity'] = GRAVITY_COMMENT; - $datarray['guid'] = $guid; - $datarray['uri'] = $message_id; - $datarray['parent-uri'] = $parent_item['uri']; - - // No timestamps for comments? OK, we'll the use current time. - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert(); - $datarray['private'] = $parent_item['private']; - - $datarray['owner-name'] = $parent_item['owner-name']; - $datarray['owner-link'] = $parent_item['owner-link']; - $datarray['owner-avatar'] = $parent_item['owner-avatar']; - - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['body'] = $body; - $datarray["object"] = json_encode($xml); - $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; - - // We can't be certain what the original app is if the message is relayed. - if(($parent_item['origin']) && (! $parent_author_signature)) - $datarray['app'] = 'Diaspora'; - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - $datarray['id'] = $message_id; - - //if($message_id) { - //q("update item set plink = '%s' where id = %d", - // //dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), - // dbesc($a->get_baseurl().'/display/'.$datarray['guid']), - // intval($message_id) - //); - //} - - // If we are the origin of the parent we store the original signature and notify our followers - if($parent_item['origin']) { - $author_signature_base64 = base64_encode($author_signature); - $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle); - - q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($message_id), - dbesc($signed_data), - dbesc($author_signature_base64), - dbesc($diaspora_handle) - ); - - // notify others - proc_run('php','include/notifier.php','comment-import',$message_id); - } - - return; -} - - - - -function diaspora_conversation($importer,$xml,$msg) { - - $a = get_app(); - - $guid = notags(unxmlify($xml->guid)); - $subject = notags(unxmlify($xml->subject)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $participant_handles = notags(unxmlify($xml->participant_handles)); - $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); - - $parent_uri = $diaspora_handle . ':' . $guid; - - $messages = $xml->message; - - if(! count($messages)) { - logger('diaspora_conversation: empty conversation'); - return; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_conversation: cannot find contact: ' . $msg['author']); - return; - } - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { - logger('diaspora_conversation: Ignoring this author.'); - return 202; - } - - $conversation = null; - - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - else { - $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ", - intval($importer['uid']), - dbesc($guid), - dbesc($diaspora_handle), - dbesc(datetime_convert('UTC','UTC',$created_at)), - dbesc(datetime_convert()), - dbesc($subject), - dbesc($participant_handles) - ); - if($r) - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - } - if(! $conversation) { - logger('diaspora_conversation: unable to create conversation.'); - return; - } - - foreach($messages as $mesg) { + private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { + $guid = notags(unxmlify($data->guid)); + $subject = notags(unxmlify($data->subject)); + $author = notags(unxmlify($data->author)); $reply = 0; @@ -1705,1469 +930,1624 @@ function diaspora_conversation($importer,$xml,$msg) { $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))); - $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle)); + $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 + if ($mesg->author) + $msg_author = notags(unxmlify($mesg->author)); + elseif ($mesg->diaspora_handle) + $msg_author = notags(unxmlify($mesg->diaspora_handle)); + else + return false; + $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); + if($msg_conversation_guid != $guid) { - logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml); - continue; + logger("message conversation guid does not belong to the current conversation."); + return false; } $body = diaspora2bb($msg_text); - $message_id = $msg_diaspora_handle . ':' . $msg_guid; + $message_uri = $msg_author.":".$msg_guid; - $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + $author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid; $author_signature = base64_decode($msg_author_signature); - if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) { + if(strcasecmp($msg_author,$msg["author"]) == 0) { $person = $contact; - $key = $msg['key']; - } - else { - $person = find_diaspora_person_by_handle($msg_diaspora_handle); + $key = $msg["key"]; + } else { + $person = self::person_by_handle($msg_author); - if(is_array($person) && x($person,'pubkey')) - $key = $person['pubkey']; + if (is_array($person) && x($person, "pubkey")) + $key = $person["pubkey"]; else { - logger('diaspora_conversation: unable to find author details'); - continue; + logger("unable to find author details"); + return false; } } - if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_conversation: verification failed.'); - continue; + 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_diaspora_handle . ';' . $msg_conversation_guid; + $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']; + $key = $msg["key"]; - if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_conversation: owner verification failed.'); - continue; + 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_id) + $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' LIMIT 1", + dbesc($message_uri) ); - if(count($r)) { - logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG); - continue; + if($r) { + logger("duplicate message already delivered.", LOGGER_DEBUG); + return false; } - q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer['uid']), + q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) + VALUES (%d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + intval($importer["uid"]), dbesc($msg_guid), - intval($conversation['id']), - dbesc($person['name']), - dbesc($person['photo']), - dbesc($person['url']), - intval($contact['id']), + intval($conversation["id"]), + dbesc($person["name"]), + dbesc($person["photo"]), + dbesc($person["url"]), + intval($contact["id"]), dbesc($subject), dbesc($body), 0, 0, - dbesc($message_id), - dbesc($parent_uri), + dbesc($message_uri), + dbesc($author.":".$guid), dbesc($msg_created_at) ); - q("update conv set updated = '%s' where id = %d", + q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", dbesc(datetime_convert()), - intval($conversation['id']) + intval($conversation["id"]) ); notification(array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' =>$importer['uid'], - 'item' => array('subject' => $subject, 'body' => $body), - 'source_name' => $person['name'], - 'source_link' => $person['url'], - 'source_photo' => $person['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' + "type" => NOTIFY_MAIL, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" =>$importer["uid"], + "item" => array("subject" => $subject, "body" => $body), + "source_name" => $person["name"], + "source_link" => $person["url"], + "source_photo" => $person["thumb"], + "verb" => ACTIVITY_POST, + "otype" => "mail" )); } - return; -} + private function receive_conversation($importer, $msg, $data) { + $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)); -function diaspora_message($importer,$xml,$msg) { + $messages = $data->message; - $a = get_app(); - - $msg_guid = notags(unxmlify($xml->guid)); - $msg_parent_guid = notags(unxmlify($xml->parent_guid)); - $msg_parent_author_signature = notags(unxmlify($xml->parent_author_signature)); - $msg_author_signature = notags(unxmlify($xml->author_signature)); - $msg_text = unxmlify($xml->text); - $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); - $msg_diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $msg_conversation_guid = notags(unxmlify($xml->conversation_guid)); - - $parent_uri = $msg_diaspora_handle . ':' . $msg_parent_guid; - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg_diaspora_handle); - if(! $contact) { - logger('diaspora_message: cannot find contact: ' . $msg_diaspora_handle); - return; - } - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { - logger('diaspora_message: Ignoring this author.'); - return 202; - } - - $conversation = null; - - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($msg_conversation_guid) - ); - if(count($c)) - $conversation = $c[0]; - else { - logger('diaspora_message: conversation not available.'); - return; - } - - $reply = 0; - - $body = diaspora2bb($msg_text); - $message_id = $msg_diaspora_handle . ':' . $msg_guid; - - $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($xml->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; - - - $author_signature = base64_decode($msg_author_signature); - - $person = find_diaspora_person_by_handle($msg_diaspora_handle); - if(is_array($person) && x($person,'pubkey')) - $key = $person['pubkey']; - else { - logger('diaspora_message: unable to find author details'); - return; - } - - if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_message: verification failed.'); - return; - } - - $r = q("select id from mail where `uri` = '%s' and uid = %d limit 1", - dbesc($message_id), - intval($importer['uid']) - ); - if(count($r)) { - logger('diaspora_message: duplicate message already delivered.', LOGGER_DEBUG); - return; - } - - q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer['uid']), - dbesc($msg_guid), - intval($conversation['id']), - dbesc($person['name']), - dbesc($person['photo']), - dbesc($person['url']), - intval($contact['id']), - dbesc($conversation['subject']), - dbesc($body), - 0, - 1, - dbesc($message_id), - dbesc($parent_uri), - dbesc($msg_created_at) - ); - - q("update conv set updated = '%s' where id = %d", - dbesc(datetime_convert()), - intval($conversation['id']) - ); - - return; -} - -function diaspora_participation($importer,$xml) { - logger("Unsupported message type 'participation' ".print_r($xml, true)); -} - -function diaspora_photo($importer,$xml,$msg,$attempt=1) { - - $a = get_app(); - - logger('diaspora_photo: init',LOGGER_DEBUG); - - $remote_photo_path = notags(unxmlify($xml->remote_photo_path)); - - $remote_photo_name = notags(unxmlify($xml->remote_photo_name)); - - $status_message_guid = notags(unxmlify($xml->status_message_guid)); - - $guid = notags(unxmlify($xml->guid)); - - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - $public = notags(unxmlify($xml->public)); - - $created_at = notags(unxmlify($xml_created_at)); - - logger('diaspora_photo: status_message_guid: ' . $status_message_guid, LOGGER_DEBUG); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_photo: contact record not found: ' . $msg['author'] . ' handle: ' . $diaspora_handle); - return; - } - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_photo: Ignoring this author.'); - return 202; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($status_message_guid) - ); - -/* deactivated by now since it can lead to multiplicated pictures in posts. - if(!count($r)) { - $result = diaspora_store_by_guid($status_message_guid, $contact['url'], $importer['uid']); - - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($status_message_guid, $person['url'], $importer['uid']); + if (!count($messages)) { + logger("empty conversation"); + return false; } - if ($result) { - logger("Fetched missing item ".$status_message_guid." - result: ".$result, LOGGER_DEBUG); + $contact = self::allowed_contact_by_handle($importer, $msg["author"], true); + if (!$contact) + return false; - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($status_message_guid) + $conversation = null; + + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if($c) + $conversation = $c[0]; + else { + $r = q("INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`) + VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s')", + intval($importer["uid"]), + dbesc($guid), + dbesc($author), + dbesc(datetime_convert("UTC", "UTC", $created_at)), + dbesc(datetime_convert()), + dbesc($subject), + dbesc($participants) + ); + if($r) + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + + if($c) + $conversation = $c[0]; + } + if (!$conversation) { + logger("unable to create conversation."); + return; + } + + foreach($messages as $mesg) + self::receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation); + + return true; + } + + private function construct_like_body($contact, $parent_item, $guid) { + $bodyverb = t('%1$s likes %2$s\'s %3$s'); + + $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; + $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]"; + $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]"; + + return sprintf($bodyverb, $ulink, $alink, $plink); + } + + private function construct_like_object($importer, $parent_item) { + $objtype = ACTIVITY_OBJ_NOTE; + $link = '<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'; + $parent_body = $parent_item["body"]; + + $xmldata = array("object" => array("type" => $objtype, + "local" => "1", + "id" => $parent_item["uri"], + "link" => $link, + "title" => "", + "content" => $parent_body)); + + return xml::from_array($xmldata, $xml, true); + } + + private 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)); + + // 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. + if (!in_array($parent_type, array("Post", "Comment"))) + return false; + + $contact = self::allowed_contact_by_handle($importer, $sender, true); + if (!$contact) + return false; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; + + $person = self::person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author details"); + return false; + } + + // Fetch the contact id - if we know this contact + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); + + // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora + // We would accept this anyhow. + if ($positive === "true") + $verb = ACTIVITY_LIKE; + else + $verb = ACTIVITY_DISLIKE; + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; + + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); + + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; + + $datarray["type"] = "activity"; + $datarray["verb"] = $verb; + $datarray["gravity"] = GRAVITY_LIKE; + $datarray["parent-uri"] = $parent_item["uri"]; + + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + $datarray["object"] = self::construct_like_object($importer, $parent_item); + + $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); + + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // We now store the raw data so that we are more flexible. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($message_id), + dbesc(json_encode($data)) + ); + + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); + } + + return $message_id; + } + + private function receive_message($importer, $data) { + $guid = notags(unxmlify($data->guid)); + $parent_guid = notags(unxmlify($data->parent_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) + return false; + + $conversation = null; + + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($conversation_guid) + ); + if($c) + $conversation = $c[0]; + else { + logger("conversation not available."); + return false; + } + + $reply = 0; + + $body = diaspora2bb($text); + $message_uri = $author.":".$guid; + + $person = self::person_by_handle($author); + if (!$person) { + logger("unable to find author details"); + return false; + } + + $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($message_uri), + intval($importer["uid"]) + ); + if($r) { + logger("duplicate message already delivered.", LOGGER_DEBUG); + return false; + } + + q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) + VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + intval($importer["uid"]), + dbesc($guid), + intval($conversation["id"]), + dbesc($person["name"]), + dbesc($person["photo"]), + dbesc($person["url"]), + intval($contact["id"]), + dbesc($conversation["subject"]), + dbesc($body), + 0, + 1, + dbesc($message_uri), + dbesc($author.":".$parent_guid), + dbesc($created_at) + ); + + q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", + dbesc(datetime_convert()), + intval($conversation["id"]) + ); + + return true; + } + + private function receive_participation($importer, $data) { + // I'm not sure if we can fully support this message type + return true; + } + + private function receive_photo($importer, $data) { + // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well + return true; + } + + private function receive_poll_participation($importer, $data) { + // We don't support polls by now + return true; + } + + private function receive_profile($importer, $data) { + $author = notags(unxmlify($data->author)); + + $contact = self::contact_by_handle($importer["uid"], $author); + if (!$contact) + return; + + $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); + $searchable = (unxmlify($data->searchable) == "true"); + $nsfw = (unxmlify($data->nsfw) == "true"); + $tags = unxmlify($data->tag_string); + + $tags = explode("#", $tags); + + $keywords = array(); + foreach ($tags as $tag) { + $tag = trim(strtolower($tag)); + if ($tag != "") + $keywords[] = $tag; + } + + $keywords = implode(", ", $keywords); + + $handle_parts = explode("@", $author); + $nick = $handle_parts[0]; + + if($name === "") + $name = $handle_parts[0]; + + if( preg_match("|^https?://|", $image_url) === 0) + $image_url = "http://".$handle_parts[1].$image_url; + + update_contact_avatar($image_url, $importer["uid"], $contact["id"]); + + // Generic birthday. We don't know the timezone. The year is irrelevant. + + $birthday = str_replace("1000", "1901", $birthday); + + if ($birthday != "") + $birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d"); + + // this is to prevent multiple birthday notifications in a single year + // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year + + if(substr($birthday,5) === substr($contact["bd"],5)) + $birthday = $contact["bd"]; + + $r = q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', `name-date` = '%s', `bd` = '%s', + `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d", + dbesc($name), + dbesc($nick), + dbesc($author), + dbesc(datetime_convert()), + dbesc($birthday), + dbesc($location), + dbesc($about), + dbesc($keywords), + dbesc($gender), + intval($contact["id"]), + intval($importer["uid"]) + ); + + if ($searchable) { + poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", + datetime_convert(), 2, $contact["id"], $importer["uid"]); + } + + $gcontact = array("url" => $contact["url"], "network" => NETWORK_DIASPORA, "generation" => 2, + "photo" => $image_url, "name" => $name, "location" => $location, + "about" => $about, "birthday" => $birthday, "gender" => $gender, + "addr" => $author, "nick" => $nick, "keywords" => $keywords, + "hide" => !$searchable, "nsfw" => $nsfw); + + update_gcontact($gcontact); + + logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG); + + return true; + } + + private function receive_request_make_friend($importer, $contact) { + + $a = get_app(); + + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { + q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", + intval(CONTACT_IS_FRIEND), + intval($contact["id"]), + intval($importer["uid"]) ); } + // send notification + + $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", + intval($importer["uid"]) + ); + + if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) { + + $self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", + intval($importer["uid"]) + ); + + // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array + + if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) { + + $arr = array(); + $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]); + $arr["uid"] = $importer["uid"]; + $arr["contact-id"] = $self[0]["id"]; + $arr["wall"] = 1; + $arr["type"] = 'wall'; + $arr["gravity"] = 0; + $arr["origin"] = 1; + $arr["author-name"] = $arr["owner-name"] = $self[0]["name"]; + $arr["author-link"] = $arr["owner-link"] = $self[0]["url"]; + $arr["author-avatar"] = $arr["owner-avatar"] = $self[0]["thumb"]; + $arr["verb"] = ACTIVITY_FRIEND; + $arr["object-type"] = ACTIVITY_OBJ_PERSON; + + $A = "[url=".$self[0]["url"]."]".$self[0]["name"]."[/url]"; + $B = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; + $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]"; + $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto; + + $arr["object"] = "<object><type>".ACTIVITY_OBJ_PERSON."</type><title>".$contact["name"]."</title>" + ."<id>".$contact["url"]."/".$contact["name"]."</id>"; + $arr["object"] .= "<link>".xmlify('<link rel="alternate" type="text/html" href="'.$contact["url"].'" />'."\n"); + $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="'.$contact["thumb"].'" />'."\n"); + $arr["object"] .= "</link></object>\n"; + $arr["last-child"] = 1; + + $arr["allow_cid"] = $user[0]["allow_cid"]; + $arr["allow_gid"] = $user[0]["allow_gid"]; + $arr["deny_cid"] = $user[0]["deny_cid"]; + $arr["deny_gid"] = $user[0]["deny_gid"]; + + $i = item_store($arr); + if($i) + proc_run("php", "include/notifier.php", "activity", $i); + + } + + } } + + private function receive_request($importer, $data) { + $author = unxmlify($data->author); + $recipient = unxmlify($data->recipient); + + if (!$author || !$recipient) + return; + + $contact = self::contact_by_handle($importer["uid"],$author); + + if($contact) { + + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + + self::receive_request_make_friend($importer, $contact); + return true; + } + + $ret = self::person_by_handle($author); + + if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { + logger("Cannot resolve diaspora handle ".$author." for ".$recipient); + return false; + } + + $batch = (($ret["batch"]) ? $ret["batch"] : implode("/", array_slice(explode("/", $ret["url"]), 0, 3))."/receive/public"); + + $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) + VALUES (%d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d)", + intval($importer["uid"]), + dbesc($ret["network"]), + dbesc($ret["addr"]), + datetime_convert(), + dbesc($ret["url"]), + dbesc(normalise_link($ret["url"])), + dbesc($batch), + dbesc($ret["name"]), + dbesc($ret["nick"]), + dbesc($ret["photo"]), + dbesc($ret["pubkey"]), + dbesc($ret["notify"]), + dbesc($ret["poll"]), + 1, + 2 + ); + + // find the contact record we just created + + $contact_record = self::contact_by_handle($importer["uid"],$author); + + if (!$contact_record) { + logger("unable to locate newly created contact record."); + return; + } + + $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", + intval($importer["uid"]) + ); + + if($g && intval($g[0]["def_gid"])) + group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]); + + if($importer["page-flags"] == PAGE_NORMAL) { + + $hash = random_string().(string)time(); // Generate a confirm_key + + $ret = q("INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`) + VALUES (%d, %d, %d, %d, '%s', '%s', '%s')", + intval($importer["uid"]), + intval($contact_record["id"]), + 0, + 0, + dbesc(t("Sharing notification from Diaspora network")), + dbesc($hash), + dbesc(datetime_convert()) + ); + } else { + + // automatic friend approval + + update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]); + + // technically they are sharing with us (CONTACT_IS_SHARING), + // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX + // we are going to change the relationship and make them a follower. + + if($importer["page-flags"] == PAGE_FREELOVE) + $new_relation = CONTACT_IS_FRIEND; + else + $new_relation = CONTACT_IS_FOLLOWER; + + $r = q("UPDATE `contact` SET `rel` = %d, + `name-date` = '%s', + `uri-date` = '%s', + `blocked` = 0, + `pending` = 0, + `writable` = 1 + WHERE `id` = %d + ", + intval($new_relation), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($contact_record["id"]) + ); + + $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); + if($u) + $ret = self::send_share($u[0], $contact_record); + } + + return true; + } + + private function original_item($guid, $orig_author, $author) { + + // Do we already have this item? + $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, + `author-name`, `author-link`, `author-avatar` + FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", + dbesc($guid)); + + if($r) { + logger("reshared message ".$guid." already exists on system."); + + // Maybe it is already a reshared item? + // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares + if (self::is_reshare($r[0]["body"])) + $r = array(); + else + return $r[0]; + } + + if (!$r) { + $server = "https://".substr($orig_author, strpos($orig_author, "@") + 1); + logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); + $item_id = self::store_by_guid($guid, $server); + + if (!$item_id) { + $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1); + logger("2nd try: reshared message ".$guid." will be fetched from original server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } + + // Deactivated by now since there is a risk that someone could manipulate postings through this method +/* if (!$item_id) { + $server = "https://".substr($author, strpos($author, "@") + 1); + logger("3rd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } + if (!$item_id) { + $server = "http://".substr($author, strpos($author, "@") + 1); + logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } */ - if(!count($r)) { - if($attempt <= 3) { - q("INSERT INTO dsprphotoq (uid, msg, attempt) VALUES (%d, '%s', %d)", - intval($importer['uid']), - dbesc(serialize($msg)), - intval($attempt + 1) - ); - } + if ($item_id) { + $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, + `author-name`, `author-link`, `author-avatar` + FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", + intval($item_id)); - logger('diaspora_photo: attempt = ' . $attempt . '; status message not found: ' . $status_message_guid . ' for photo: ' . $guid); - return; - } + if ($r) + return $r[0]; - $parent_item = $r[0]; - - $link_text = '[img]' . $remote_photo_path . $remote_photo_name . '[/img]' . "\n"; - - $link_text = scale_external_images($link_text, true, - array($remote_photo_name, 'scaled_full_' . $remote_photo_name)); - - if(strpos($parent_item['body'],$link_text) === false) { - - $parent_item['body'] = $link_text . $parent_item['body']; - - $r = q("UPDATE `item` SET `body` = '%s', `visible` = 1 WHERE `id` = %d AND `uid` = %d", - dbesc($parent_item['body']), - intval($parent_item['id']), - intval($parent_item['uid']) - ); - put_item_in_cache($parent_item, true); - update_thread($parent_item['id']); - } - - return; -} - - - - -function diaspora_like($importer,$xml,$msg) { - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $parent_guid = notags(unxmlify($xml->parent_guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $target_type = notags(unxmlify($xml->target_type)); - $positive = notags(unxmlify($xml->positive)); - $author_signature = notags(unxmlify($xml->author_signature)); - - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); - - // likes on comments not supported here and likes on photos not supported by Diaspora - -// if($target_type !== 'Post') -// return; - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_like: cannot find contact: ' . $msg['author']); - return; - } - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_like: Ignoring this author.'); - return 202; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - - if(!count($r)) { - $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); - - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); - } - - if ($result) { - logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG); - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - } - } - - if(! count($r)) { - logger('diaspora_like: parent item not found: ' . $guid); - return; - } - - $parent_item = $r[0]; - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - if($positive === 'true') { - logger('diaspora_like: duplicate like: ' . $guid); - return; - } - // Note: I don't think "Like" objects with positive = "false" are ever actually used - // It looks like "RelayableRetractions" are used for "unlike" instead - if($positive === 'false') { - logger('diaspora_like: received a like with positive set to "false"...ignoring'); -/* q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d", - intval($r[0]['id']), - intval($importer['uid']) - );*/ - // FIXME--actually don't unless it turns out that Diaspora does indeed send out "false" likes - // send notification via proc_run() - return; - } - } - // Note: I don't think "Like" objects with positive = "false" are ever actually used - // It looks like "RelayableRetractions" are used for "unlike" instead - if($positive === 'false') { - logger('diaspora_like: received a like with positive set to "false"'); - logger('diaspora_like: unlike received with no corresponding like...ignoring'); - return; - } - - - /* How Diaspora performs "like" signature checking: - - - If an item has been sent by the like author to the top-level post owner to relay on - to the rest of the contacts on the top-level post, the top-level post owner should check - the author_signature, then create a parent_author_signature before relaying the like on - - If an item has been relayed on by the top-level post owner, the contacts who receive it - check only the parent_author_signature. Basically, they trust that the top-level post - owner has already verified the authenticity of anything he/she sends out - - In either case, the signature that get checked is the signature created by the person - who sent the salmon - */ - - // Diaspora has changed the way they are signing the likes. - // Just to make sure that we don't miss any likes we will check the old and the current way. - $old_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle; - - $signed_data = $positive . ';' . $guid . ';' . $target_type . ';' . $parent_guid . ';' . $diaspora_handle; - - $key = $msg['key']; - - if ($parent_author_signature) { - // If a parent_author_signature exists, then we've received the like - // relayed from the top-level post owner. There's no need to check the - // author_signature if the parent_author_signature is valid - - $parent_author_signature = base64_decode($parent_author_signature); - - if (!rsa_verify($signed_data,$parent_author_signature,$key,'sha256') AND - !rsa_verify($old_signed_data,$parent_author_signature,$key,'sha256')) { - - logger('diaspora_like: top-level owner verification failed.'); - return; - } - } else { - // If there's no parent_author_signature, then we've received the like - // from the like creator. In that case, the person is "like"ing - // our post, so he/she must be a contact of ours and his/her public key - // should be in $msg['key'] - - $author_signature = base64_decode($author_signature); - - if (!rsa_verify($signed_data,$author_signature,$key,'sha256') AND - !rsa_verify($old_signed_data,$author_signature,$key,'sha256')) { - - logger('diaspora_like: like creator verification failed.'); - return; - } - } - - // Phew! Everything checks out. Now create an item. - - // Find the original comment author information. - // We need this to make sure we display the comment author - // information (name and avatar) correctly. - if(strcasecmp($diaspora_handle,$msg['author']) == 0) - $person = $contact; - else { - $person = find_diaspora_person_by_handle($diaspora_handle); - - if(! is_array($person)) { - logger('diaspora_like: unable to find author details'); - return; - } - } - - $uri = $diaspora_handle . ':' . $guid; - - $activity = ACTIVITY_LIKE; - $post_type = (($parent_item['resource-id']) ? t('photo') : t('status')); - $objtype = (($parent_item['resource-id']) ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE ); - $link = xmlify('<link rel="alternate" type="text/html" href="' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . '" />' . "\n") ; - $body = $parent_item['body']; - - $obj = <<< EOT - - <object> - <type>$objtype</type> - <local>1</local> - <id>{$parent_item['uri']}</id> - <link>$link</link> - <title></title> - <content>$body</content> - </object> -EOT; - $bodyverb = t('%1$s likes %2$s\'s %3$s'); - - // Fetch the contact id - if we know this contact - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person['url'])), intval($importer['uid'])); - if ($r) { - $cid = $r[0]['id']; - $network = $r[0]['network']; - } else { - $cid = $contact['id']; - $network = NETWORK_DIASPORA; - } - - $arr = array(); - - $arr['uri'] = $uri; - $arr['uid'] = $importer['uid']; - $arr['guid'] = $guid; - $arr['network'] = $network; - $arr['contact-id'] = $cid; - $arr['type'] = 'activity'; - $arr['wall'] = $parent_item['wall']; - $arr['gravity'] = GRAVITY_LIKE; - $arr['parent'] = $parent_item['id']; - $arr['parent-uri'] = $parent_item['uri']; - - $arr['owner-name'] = $parent_item['name']; - $arr['owner-link'] = $parent_item['url']; - //$arr['owner-avatar'] = $parent_item['thumb']; - $arr['owner-avatar'] = ((x($parent_item,'thumb')) ? $parent_item['thumb'] : $parent_item['photo']); - - $arr['author-name'] = $person['name']; - $arr['author-link'] = $person['url']; - $arr['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - - $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; - $alink = '[url=' . $parent_item['author-link'] . ']' . $parent_item['author-name'] . '[/url]'; - //$plink = '[url=' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . ']' . $post_type . '[/url]'; - $plink = '[url='.$a->get_baseurl().'/display/'.urlencode($guid).']'.$post_type.'[/url]'; - $arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink ); - - $arr['app'] = 'Diaspora'; - - $arr['private'] = $parent_item['private']; - $arr['verb'] = $activity; - $arr['object-type'] = $objtype; - $arr['object'] = $obj; - $arr['visible'] = 1; - $arr['unseen'] = 1; - $arr['last-child'] = 0; - - $message_id = item_store($arr); - - - //if($message_id) { - // q("update item set plink = '%s' where id = %d", - // //dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), - // dbesc($a->get_baseurl().'/display/'.$guid), - // intval($message_id) - // ); - //} - - // If we are the origin of the parent we store the original signature and notify our followers - if($parent_item['origin']) { - $author_signature_base64 = base64_encode($author_signature); - $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle); - - q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($message_id), - dbesc($signed_data), - dbesc($author_signature_base64), - dbesc($diaspora_handle) - ); - - // notify others - proc_run('php','include/notifier.php','comment-import',$message_id); - } - - return; -} - -function diaspora_retraction($importer,$xml) { - - - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $type = notags(unxmlify($xml->type)); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - if($type === 'Person') { - require_once('include/Contact.php'); - contact_remove($contact['id']); - } elseif($type === 'StatusMessage') { - $guid = notags(unxmlify($xml->post_guid)); - - $r = q("SELECT * FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1", - dbesc($guid), - intval($importer['uid']) - ); - if(count($r)) { - if(link_compare($r[0]['author-link'],$contact['url'])) { - q("UPDATE `item` SET `deleted` = 1, `changed` = '%s' WHERE `id` = %d", - dbesc(datetime_convert()), - intval($r[0]['id']) - ); - delete_thread($r[0]['id'], $r[0]['parent-uri']); } } - } elseif($type === 'Post') { - $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", - dbesc('guid'), - intval($importer['uid']) + return false; + } + + private function receive_reshare($importer, $data) { + $root_author = notags(unxmlify($data->root_author)); + $root_guid = notags(unxmlify($data->root_guid)); + $guid = notags(unxmlify($data->guid)); + $author = notags(unxmlify($data->author)); + $public = notags(unxmlify($data->public)); + $created_at = notags(unxmlify($data->created_at)); + + $contact = self::allowed_contact_by_handle($importer, $author, false); + if (!$contact) + return false; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $original_item = self::original_item($root_guid, $root_author, $author); + if (!$original_item) + return false; + + $orig_url = App::get_baseurl()."/display/".$original_item["guid"]; + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; + + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; + + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; + + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; + + $datarray["object"] = json_encode($data); + + $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], + $original_item["guid"], $original_item["created"], $orig_url); + $datarray["body"] = $prefix.$original_item["body"]."[/share]"; + + $datarray["tag"] = $original_item["tag"]; + $datarray["app"] = $original_item["app"]; + + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); + + $datarray["object-type"] = $original_item["object-type"]; + + self::fetch_guid($datarray); + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + return $message_id; + } + + private 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)); + + $person = self::person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author detail for ".$author); + return false; + } + + $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(count($r)) { - if(link_compare($r[0]['author-link'],$contact['url'])) { - q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d", - dbesc(datetime_convert()), - intval($r[0]['id']) - ); - delete_thread($r[0]['id'], $r[0]['parent-uri']); + if (!$r) + return false; + + // Only delete it if the author really fits + if (!link_compare($r[0]["author-link"], $person["url"])) { + logger("Item author ".$r[0]["author-link"]." doesn't fit to expected contact ".$person["url"], LOGGER_DEBUG); + return false; + } + + // Check if the sender is the thread owner + $p = q("SELECT `id`, `author-link`, `origin` FROM `item` WHERE `id` = %d", + intval($r[0]["parent"])); + + // Only delete it if the parent author really fits + if (!link_compare($p[0]["author-link"], $contact["url"]) AND !link_compare($r[0]["author-link"], $contact["url"])) { + logger("Thread author ".$p[0]["author-link"]." and item author ".$r[0]["author-link"]." don't fit to expected contact ".$contact["url"], LOGGER_DEBUG); + return false; + } + + // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case + q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($r[0]["id"]) + ); + delete_thread($r[0]["id"], $r[0]["parent-uri"]); + + logger("Deleted target ".$target_guid." (".$r[0]["id"].") from user ".$importer["uid"]." parent: ".$p[0]["id"], LOGGER_DEBUG); + + // Now check if the retraction needs to be relayed by us + if($p[0]["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // We now store the raw data so that we are more flexible. + q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", + intval($r[0]["id"]), + dbesc(json_encode($data)) + ); + $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); + logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); + + // notify others + proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); + } + } + + private function receive_retraction($importer, $sender, $data) { + $target_type = notags(unxmlify($data->target_type)); + + $contact = self::contact_by_handle($importer["uid"], $sender); + if (!$contact) { + logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]); + return false; + } + + logger("Got retraction for ".$target_type.", sender ".$sender." and user ".$importer["uid"], LOGGER_DEBUG); + + switch ($target_type) { + case "Comment": + case "Like": + case "Post": // "Post" will be supported in a future version + case "Reshare": + case "StatusMessage": + return self::item_retraction($importer, $contact, $data);; + + case "Person": + /// @todo What should we do with an "unshare"? + // Removing the contact isn't correct since we still can read the public items + //contact_remove($contact["id"]); + return true; + + default: + logger("Unknown target type ".$target_type); + return false; + } + return true; + } + + private function receive_status_message($importer, $data) { + + $raw_message = unxmlify($data->raw_message); + $guid = notags(unxmlify($data->guid)); + $author = notags(unxmlify($data->author)); + $public = notags(unxmlify($data->public)); + $created_at = notags(unxmlify($data->created_at)); + $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; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $address = array(); + if ($data->location) + foreach ($data->location->children() AS $fieldname => $data) + $address[$fieldname] = notags(unxmlify($data)); + + $body = diaspora2bb($raw_message); + + $datarray = array(); + + if ($data->photo) { + foreach ($data->photo AS $photo) + $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; + + $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; + } else { + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + + // Add OEmbed and other information to the body + if (!self::is_redmatrix($contact["url"])) + $body = add_page_info_to_body($body, false, true); + } + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; + + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; + + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; + + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; + + $datarray["object"] = json_encode($data); + + $datarray["body"] = $body; + + if ($provider_display_name != "") + $datarray["app"] = $provider_display_name; + + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); + + if (isset($address["address"])) + $datarray["location"] = $address["address"]; + + if (isset($address["lat"]) AND isset($address["lng"])) + $datarray["coord"] = $address["lat"]." ".$address["lng"]; + + self::fetch_guid($datarray); + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + return $message_id; + } + + /****************************************************************************************** + * Here are all the functions that are needed to transmit data with the Diaspora protocol * + ******************************************************************************************/ + + private function my_handle($me) { + if ($contact["addr"] != "") + return $contact["addr"]; + + // Normally we should have a filled "addr" field - but in the past this wasn't the case + // So - just in case - we build the the address here. + return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); + } + + private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { + + logger("Message: ".$msg, LOGGER_DATA); + + $handle = self::my_handle($user); + + $b64url_data = base64url_encode($msg); + + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); + + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; + + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); + + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); + + $xmldata = array("diaspora" => array("header" => array("author_id" => $handle), + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "@attributes" => array("type" => "application/xml"), + "me:sig" => $sig))); + + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); + + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { + + logger("Message: ".$msg, LOGGER_DATA); + + // without a public key nothing will work + + if (!$pubkey) { + logger("pubkey missing: contact id: ".$contact["id"]); + return false; + } + + $inner_aes_key = random_string(32); + $b_inner_aes_key = base64_encode($inner_aes_key); + $inner_iv = random_string(16); + $b_inner_iv = base64_encode($inner_iv); + + $outer_aes_key = random_string(32); + $b_outer_aes_key = base64_encode($outer_aes_key); + $outer_iv = random_string(16); + $b_outer_iv = base64_encode($outer_iv); + + $handle = self::my_handle($user); + + $padded_data = pkcs5_pad($msg,16); + $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); + + $b64_data = base64_encode($inner_encrypted); + + + $b64url_data = base64url_encode($b64_data); + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); + + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; + + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); + + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); + + $xmldata = array("decrypted_header" => array("iv" => $b_inner_iv, + "aes_key" => $b_inner_aes_key, + "author_id" => $handle)); + + $decrypted_header = xml::from_array($xmldata, $xml, true); + $decrypted_header = pkcs5_pad($decrypted_header,16); + + $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); + + $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key)); + + $encrypted_outer_key_bundle = ""; + openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey); + + $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); + + logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA); + + $encrypted_header_json_object = json_encode(array("aes_key" => base64_encode($encrypted_outer_key_bundle), + "ciphertext" => base64_encode($ciphertext))); + $cipher_json = base64_encode($encrypted_header_json_object); + + $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "@attributes" => array("type" => "application/xml"), + "me:sig" => $sig))); + + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); + + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { + + if ($public) + $magic_env = self::build_public_message($msg,$user,$contact,$prvkey,$pubkey); + else + $magic_env = self::build_private_message($msg,$user,$contact,$prvkey,$pubkey); + + // The data that will be transmitted is double encoded via "urlencode", strange ... + $slap = "xml=".urlencode(urlencode($magic_env)); + return $slap; + } + + private function signature($owner, $message) { + $sigmsg = $message; + unset($sigmsg["author_signature"]); + unset($sigmsg["parent_author_signature"]); + + $signed_text = implode(";", $sigmsg); + + return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } + + public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { + + $a = get_app(); + + $enabled = intval(get_config("system", "diaspora_enabled")); + if(!$enabled) + return 200; + + $logid = random_string(4); + $dest_url = (($public_batch) ? $contact["batch"] : $contact["notify"]); + if (!$dest_url) { + logger("no url for contact: ".$contact["id"]." batch mode =".$public_batch); + return 0; + } + + logger("transmit: ".$logid."-".$guid." ".$dest_url); + + if (!$queue_run && was_recently_delayed($contact["id"])) { + $return_code = 0; + } else { + if (!intval(get_config("system", "diaspora_test"))) { + post_url($dest_url."/", $slap); + $return_code = $a->get_curl_code(); + } else { + logger("test_mode"); + return 200; } } - } - return 202; - // NOTREACHED -} + logger("transmit: ".$logid."-".$guid." returns: ".$return_code); -function diaspora_signed_retraction($importer,$xml,$msg) { + if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) { + logger("queue message"); - - $guid = notags(unxmlify($xml->target_guid)); - $diaspora_handle = notags(unxmlify($xml->sender_handle)); - $type = notags(unxmlify($xml->target_type)); - $sig = notags(unxmlify($xml->target_author_signature)); - - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) { - logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['uid']); - return; - } - - - $signed_data = $guid . ';' . $type ; - $key = $msg['key']; - - /* How Diaspora performs relayable_retraction signature checking: - - - If an item has been sent by the item author to the top-level post owner to relay on - to the rest of the contacts on the top-level post, the top-level post owner checks - the author_signature, then creates a parent_author_signature before relaying the item on - - If an item has been relayed on by the top-level post owner, the contacts who receive it - check only the parent_author_signature. Basically, they trust that the top-level post - owner has already verified the authenticity of anything he/she sends out - - In either case, the signature that get checked is the signature created by the person - who sent the salmon - */ - - if($parent_author_signature) { - - $parent_author_signature = base64_decode($parent_author_signature); - - if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_signed_retraction: top-level post owner verification failed'); - return; + $r = q("SELECT `id` FROM `queue` WHERE `cid` = %d AND `network` = '%s' AND `content` = '%s' AND `batch` = %d LIMIT 1", + intval($contact["id"]), + dbesc(NETWORK_DIASPORA), + dbesc($slap), + intval($public_batch) + ); + if($r) { + logger("add_to_queue ignored - identical item already in queue"); + } else { + // queue message for redelivery + add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch); + } } - } else { - - $sig_decode = base64_decode($sig); - - if(! rsa_verify($signed_data,$sig_decode,$key,'sha256')) { - logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg,true)); - return; - } + return(($return_code) ? $return_code : (-1)); } - if($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { - $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", - dbesc($guid), - intval($importer['uid']) + + private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { + + $data = array("XML" => array("post" => array($type => $message))); + + $msg = xml::from_array($data, $xml); + + logger('message: '.$msg, LOGGER_DATA); + logger('send guid '.$guid, LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); + + if ($spool) { + add_to_queue($contact['id'], NETWORK_DIASPORA, $slap, $public_batch); + return true; + } else + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + + public static function send_share($owner,$contact) { + + $message = array("sender_handle" => self::my_handle($owner), + "recipient_handle" => $contact["addr"]); + + return self::build_and_transmit($owner, $contact, "request", $message); + } + + public static function send_unshare($owner,$contact) { + + $message = array("post_guid" => $owner["guid"], + "diaspora_handle" => self::my_handle($owner), + "type" => "Person"); + + return self::build_and_transmit($owner, $contact, "retraction", $message); + } + + public static function is_reshare($body) { + $body = trim($body); + + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(false); + + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(false); + + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + // Skip if there is no shared message in there + if ($body == $attributes) + return(false); + + $guid = ""; + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + preg_match('/guid="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + if ($guid != "") { + $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", + dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); + if ($r) { + $ret= array(); + $ret["root_handle"] = self::handle_from_contact($r[0]["contact-id"]); + $ret["root_guid"] = $guid; + return($ret); + } + } + + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + $ret= array(); + + $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); + if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) + return(false); + + $link = ""; + preg_match("/link='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + preg_match('/link="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); + if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) + return(false); + return($ret); + } + + public static function send_status($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::my_handle($owner); + + $public = (($item["private"]) ? "false" : "true"); + + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + // Detect a share element and do a reshare + if (!$item['private'] AND ($ret = self::is_reshare($item["body"]))) { + $message = array("root_diaspora_id" => $ret["root_handle"], + "root_guid" => $ret["root_guid"], + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + $type = "reshare"; + } else { + $title = $item["title"]; + $body = $item["body"]; + + // convert to markdown + $body = html_entity_decode(bb2diaspora($body)); + + // Adding the title + if(strlen($title)) + $body = "## ".html_entity_decode($title)."\n\n".$body; + + if ($item["attach"]) { + $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); + if(cnt) { + $body .= "\n".t("Attachments:")."\n"; + foreach($matches as $mtch) + $body .= "[".$mtch[3]."](".$mtch[1].")\n"; + } + } + + $location = array(); + + if ($item["location"] != "") + $location["address"] = $item["location"]; + + if ($item["coord"] != "") { + $coord = explode(" ", $item["coord"]); + $location["lat"] = $coord[0]; + $location["lng"] = $coord[1]; + } + + $message = array("raw_message" => $body, + "location" => $location, + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + if (count($location) == 0) + unset($message["location"]); + + $type = "status_message"; + } + + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); + } + + private function construct_like($item, $owner) { + + $myaddr = self::my_handle($owner); + + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"])); + if(!$p) + return false; + + $parent = $p[0]; + + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; + + return(array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $target_type, + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr)); + } + + private function construct_comment($item, $owner) { + + $myaddr = self::my_handle($owner); + + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) ); - if(count($r)) { - if(link_compare($r[0]['author-link'],$contact['url'])) { - q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d", - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($r[0]['id']) - ); - delete_thread($r[0]['id'], $r[0]['parent-uri']); - // Now check if the retraction needs to be relayed by us - // - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("SELECT `origin` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($r[0]['parent']), - intval($r[0]['parent']) - ); - if(count($p)) { - if($p[0]['origin']) { - q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - $r[0]['id'], - dbesc($signed_data), - dbesc($sig), - dbesc($diaspora_handle) - ); + if (!$p) + return false; - // the existence of parent_author_signature would have meant the parent_author or owner - // is already relaying. - logger('diaspora_signed_retraction: relaying relayable_retraction'); + $parent = $p[0]; - proc_run('php','include/notifier.php','drop',$r[0]['id']); + $text = html_entity_decode(bb2diaspora($item["body"])); + + return(array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => "", + "text" => $text, + "diaspora_handle" => $myaddr)); + } + + public static function send_followup($item,$owner,$contact,$public_batch = false) { + + if($item['verb'] === ACTIVITY_LIKE) { + $message = self::construct_like($item, $owner); + $type = "like"; + } else { + $message = self::construct_comment($item, $owner); + $type = "comment"; + } + + if (!$message) + return false; + + $message["author_signature"] = self::signature($owner, $message); + + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); + } + + private function message_from_signatur($item, $signature) { + + // Split the signed text + $signed_parts = explode(";", $signature['signed_text']); + + if ($item["deleted"]) + $message = array("parent_author_signature" => "", + "target_guid" => $signed_parts[0], + "target_type" => $signed_parts[1], + "sender_handle" => $signature['signer'], + "target_author_signature" => $signature['signature']); + elseif ($item['verb'] === ACTIVITY_LIKE) + $message = array("positive" => $signed_parts[0], + "guid" => $signed_parts[1], + "target_type" => $signed_parts[2], + "parent_guid" => $signed_parts[3], + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "diaspora_handle" => $signed_parts[4]); + else { + // Remove the comment guid + $guid = array_shift($signed_parts); + + // Remove the parent guid + $parent_guid = array_shift($signed_parts); + + // Remove the handle + $handle = array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); + + $message = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } + return $message; + } + + public static function send_relay($item, $owner, $contact, $public_batch = false) { + + if ($item["deleted"]) { + $sql_sign_id = "retract_iid"; + $type = "relayable_retraction"; + } elseif ($item['verb'] === ACTIVITY_LIKE) { + $sql_sign_id = "iid"; + $type = "like"; + } else { + $sql_sign_id = "iid"; + $type = "comment"; + } + + logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + + // fetch the original signature + + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", + intval($item["id"])); + + if (!$r) + return self::send_followup($item, $owner, $contact, $public_batch); + + $signature = $r[0]; + + // Old way - is used by the internal Friendica functions + /// @todo Change all signatur storing functions to the new format + if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) + $message = self::message_from_signatur($item, $signature); + else {// New way + $msg = json_decode($signature['signed_text'], true); + + $message = array(); + foreach ($msg AS $field => $data) { + if (!$item["deleted"]) { + if ($field == "author") + $field = "diaspora_handle"; + if ($field == "parent_type") + $field = "target_type"; + } + + $message[$field] = $data; + } + } + + if ($item["deleted"]) { + $signed_text = $message["target_guid"].';'.$message["target_type"]; + $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } else + $message["parent_author_signature"] = self::signature($owner, $message); + + logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); + + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); + } + + public static function send_retraction($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::my_handle($owner); + + // Check whether the retraction is for a top-level post or whether it's a relayable + if ($item["uri"] !== $item["parent-uri"]) { + $msg_type = "relayable_retraction"; + $target_type = (($item["verb"] === ACTIVITY_LIKE) ? "Like" : "Comment"); + } else { + $msg_type = "signed_retraction"; + $target_type = "StatusMessage"; + } + + $signed_text = $item["guid"].";".$target_type; + + $message = array("target_guid" => $item['guid'], + "target_type" => $target_type, + "sender_handle" => $myaddr, + "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); + + return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); + } + + public static function send_mail($item, $owner, $contact) { + + $myaddr = self::my_handle($owner); + + $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($item["convid"]), + intval($item["uid"]) + ); + + if (!$r) { + logger("conversation not found."); + return; + } + $cnv = $r[0]; + + $conv = array( + "guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"] + ); + + $body = bb2diaspora($item["body"]); + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; + $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + + $msg = array( + "guid" => $item["guid"], + "parent_guid" => $cnv["guid"], + "parent_author_signature" => $sig, + "author_signature" => $sig, + "text" => $body, + "created_at" => $created, + "diaspora_handle" => $myaddr, + "conversation_guid" => $cnv["guid"] + ); + + if ($item["reply"]) { + $message = $msg; + $type = "message"; + } else { + $message = array("guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "message" => $msg, + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"]); + + $type = "conversation"; + } + + return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); + } + + public static function send_profile($uid) { + + if (!$uid) + return; + + $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' + AND `uid` = %d AND `rel` != %d", + dbesc(NETWORK_DIASPORA), + intval($uid), + intval(CONTACT_IS_SHARING) + ); + if (!$recips) + return; + + $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr` + FROM `profile` + INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` + INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid` + WHERE `user`.`uid` = %d AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", + intval($uid) + ); + + if (!$r) + return; + + $profile = $r[0]; + + $handle = $profile["addr"]; + $first = ((strpos($profile['name'],' ') + ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); + $last = (($first === $profile['name']) ? '' : trim(substr($profile['name'], strlen($first)))); + $large = App::get_baseurl().'/photo/custom/300/'.$profile['uid'].'.jpg'; + $medium = App::get_baseurl().'/photo/custom/100/'.$profile['uid'].'.jpg'; + $small = App::get_baseurl().'/photo/custom/50/' .$profile['uid'].'.jpg'; + $searchable = (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false'); + + if ($searchable === 'true') { + $dob = '1000-00-00'; + + if (($profile['dob']) && ($profile['dob'] != '0000-00-00')) + $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') .'-'. datetime_convert('UTC','UTC',$profile['dob'],'m-d'); + + $about = $profile['about']; + $about = strip_tags(bbcode($about)); + + $location = formatted_location($profile); + $tags = ''; + if ($profile['pub_keywords']) { + $kw = str_replace(',',' ',$profile['pub_keywords']); + $kw = str_replace(' ',' ',$kw); + $arr = explode(' ',$profile['pub_keywords']); + if (count($arr)) { + for($x = 0; $x < 5; $x ++) { + if (trim($arr[$x])) + $tags .= '#'. trim($arr[$x]) .' '; } } } + $tags = trim($tags); } - } - else - logger('diaspora_signed_retraction: unknown type: ' . $type); - return 202; - // NOTREACHED -} - -function diaspora_profile($importer,$xml,$msg) { - - $a = get_app(); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - //if($contact['blocked']) { - // logger('diaspora_post: Ignoring this author.'); - // return 202; - //} - - $name = unxmlify($xml->first_name) . ((strlen($xml->last_name)) ? ' ' . unxmlify($xml->last_name) : ''); - $image_url = unxmlify($xml->image_url); - $birthday = unxmlify($xml->birthday); - $location = diaspora2bb(unxmlify($xml->location)); - $about = diaspora2bb(unxmlify($xml->bio)); - $gender = unxmlify($xml->gender); - $searchable = (unxmlify($xml->searchable) == "true"); - $nsfw = (unxmlify($xml->nsfw) == "true"); - $tags = unxmlify($xml->tag_string); - - $tags = explode("#", $tags); - - $keywords = array(); - foreach ($tags as $tag) { - $tag = trim(strtolower($tag)); - if ($tag != "") - $keywords[] = $tag; - } - - $keywords = implode(", ", $keywords); - - $handle_parts = explode("@", $diaspora_handle); - $nick = $handle_parts[0]; - - if($name === '') { - $name = $handle_parts[0]; - } - - if( preg_match("|^https?://|", $image_url) === 0) { - $image_url = "http://" . $handle_parts[1] . $image_url; - } - -/* $r = q("SELECT DISTINCT ( `resource-id` ) FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' ", - intval($importer['uid']), - intval($contact['id']) - ); - $oldphotos = ((count($r)) ? $r : null);*/ - - require_once('include/Photo.php'); - - update_contact_avatar($image_url,$importer['uid'],$contact['id']); - - // Generic birthday. We don't know the timezone. The year is irrelevant. - - $birthday = str_replace('1000','1901',$birthday); - - if ($birthday != "") - $birthday = datetime_convert('UTC','UTC',$birthday,'Y-m-d'); - - // this is to prevent multiple birthday notifications in a single year - // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year - - if(substr($birthday,5) === substr($contact['bd'],5)) - $birthday = $contact['bd']; - - /// @TODO Update name on item['author-name'] if the name changed. See consume_feed() - /// (Not doing this currently because D* protocol is scheduled for revision soon). - - $r = q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', `name-date` = '%s', `bd` = '%s', - `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d", - dbesc($name), - dbesc($nick), - dbesc($diaspora_handle), - dbesc(datetime_convert()), - dbesc($birthday), - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - intval($contact['id']), - intval($importer['uid']) - ); - - if ($searchable) { - require_once('include/socgraph.php'); - poco_check($contact['url'], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", - datetime_convert(), 2, $contact['id'], $importer['uid']); - } - - update_gcontact(array("url" => $contact['url'], "network" => NETWORK_DIASPORA, "generation" => 2, - "photo" => $image_url, "name" => $name, "location" => $location, - "about" => $about, "birthday" => $birthday, "gender" => $gender, - "addr" => $diaspora_handle, "nick" => $nick, "keywords" => $keywords, - "hide" => !$searchable, "nsfw" => $nsfw)); - -/* if($r) { - if($oldphotos) { - foreach($oldphotos as $ph) { - q("DELETE FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' AND `resource-id` = '%s' ", - intval($importer['uid']), - intval($contact['id']), - dbesc($ph['resource-id']) - ); - } - } - } */ - - return; - -} - -function diaspora_share($me,$contact) { - $a = get_app(); - $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - $theiraddr = $contact['addr']; - - $tpl = get_markup_template('diaspora_share.tpl'); - $msg = replace_macros($tpl, array( - '$sender' => $myaddr, - '$recipient' => $theiraddr - )); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])); - - return(diaspora_transmit($owner,$contact,$slap, false)); -} - -function diaspora_unshare($me,$contact) { - - $a = get_app(); - $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - $tpl = get_markup_template('diaspora_retract.tpl'); - $msg = replace_macros($tpl, array( - '$guid' => $me['guid'], - '$type' => 'Person', - '$handle' => $myaddr - )); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])); - - return(diaspora_transmit($owner,$contact,$slap, false)); - -} - - -function diaspora_send_status($item,$owner,$contact,$public_batch = false) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - $theiraddr = $contact['addr']; - - $images = array(); - - $title = $item['title']; - $body = $item['body']; - -/* - // We're trying to match Diaspora's split message/photo protocol but - // all the photos are displayed on D* as links and not img's - even - // though we're sending pretty much precisely what they send us when - // doing the same operation. - // Commented out for now, we'll use bb2diaspora to convert photos to markdown - // which seems to get through intact. - - $cnt = preg_match_all('|\[img\](.*?)\[\/img\]|',$body,$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - $detail = array(); - $detail['str'] = $mtch[0]; - $detail['path'] = dirname($mtch[1]) . '/'; - $detail['file'] = basename($mtch[1]); - $detail['guid'] = $item['guid']; - $detail['handle'] = $myaddr; - $images[] = $detail; - $body = str_replace($detail['str'],$mtch[1],$body); - } - } -*/ - - //if(strlen($title)) - // $body = "[b]".html_entity_decode($title)."[/b]\n\n".$body; - - // convert to markdown - $body = xmlify(html_entity_decode(bb2diaspora($body))); - //$body = bb2diaspora($body); - - // Adding the title - if(strlen($title)) - $body = "## ".html_entity_decode($title)."\n\n".$body; - - if($item['attach']) { - $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism',$item['attach'],$matches,PREG_SET_ORDER); - if(cnt) { - $body .= "\n" . t('Attachments:') . "\n"; - foreach($matches as $mtch) { - $body .= '[' . $mtch[3] . '](' . $mtch[1] . ')' . "\n"; - } - } - } - - - $public = (($item['private']) ? 'false' : 'true'); - - require_once('include/datetime.php'); - $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C'); - - // Detect a share element and do a reshare - // see: https://github.com/Raven24/diaspora-federation/blob/master/lib/diaspora-federation/entities/reshare.rb - if (!$item['private'] AND ($ret = diaspora_is_reshare($item["body"]))) { - $tpl = get_markup_template('diaspora_reshare.tpl'); - $msg = replace_macros($tpl, array( - '$root_handle' => xmlify($ret['root_handle']), - '$root_guid' => $ret['root_guid'], - '$guid' => $item['guid'], - '$handle' => xmlify($myaddr), - '$public' => $public, - '$created' => $created, - '$provider' => $item["app"] - )); - } else { - $tpl = get_markup_template('diaspora_post.tpl'); - $msg = replace_macros($tpl, array( - '$body' => $body, - '$guid' => $item['guid'], - '$handle' => xmlify($myaddr), - '$public' => $public, - '$created' => $created, - '$provider' => $item["app"] - )); - } - - logger('diaspora_send_status: '.$owner['username'].' -> '.$contact['name'].' base message: '.$msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)); - - $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']); - - logger('diaspora_send_status: guid: '.$item['guid'].' result '.$return_code, LOGGER_DEBUG); - - if(count($images)) { - diaspora_send_images($item,$owner,$contact,$images,$public_batch); - } - - return $return_code; -} - -function diaspora_is_reshare($body) { - $body = trim($body); - - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(false); - - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(false); - - $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); - // Skip if there is no shared message in there - if ($body == $attributes) - return(false); - - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - if ($guid != "") { - $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", - dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); - if ($r) { - $ret= array(); - $ret["root_handle"] = diaspora_handle_from_contact($r[0]["contact-id"]); - $ret["root_guid"] = $guid; - return($ret); - } - } - - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - $ret= array(); - - $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); - if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) - return(false); - - $link = ""; - preg_match("/link='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - preg_match('/link="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); - if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) - return(false); - - return($ret); -} - -function diaspora_send_images($item,$owner,$contact,$images,$public_batch = false) { - $a = get_app(); - if(! count($images)) - return; - $mysite = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://') + 3) . '/photo'; - - $tpl = get_markup_template('diaspora_photo.tpl'); - foreach($images as $image) { - if(! stristr($image['path'],$mysite)) - continue; - $resource = str_replace('.jpg','',$image['file']); - $resource = substr($resource,0,strpos($resource,'-')); - - $r = q("select * from photo where `resource-id` = '%s' and `uid` = %d limit 1", - dbesc($resource), - intval($owner['uid']) - ); - if(! count($r)) - continue; - $public = (($r[0]['allow_cid'] || $r[0]['allow_gid'] || $r[0]['deny_cid'] || $r[0]['deny_gid']) ? 'false' : 'true' ); - $msg = replace_macros($tpl,array( - '$path' => xmlify($image['path']), - '$filename' => xmlify($image['file']), - '$msg_guid' => xmlify($image['guid']), - '$guid' => xmlify($r[0]['guid']), - '$handle' => xmlify($image['handle']), - '$public' => xmlify($public), - '$created_at' => xmlify(datetime_convert('UTC','UTC',$r[0]['created'],'Y-m-d H:i:s \U\T\C')) - )); - - - logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$r[0]['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)); - - diaspora_transmit($owner,$contact,$slap,$public_batch,false,$r[0]['guid']); - } - -} - -function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); -// $theiraddr = $contact['addr']; - - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } - else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; - - if($item['verb'] === ACTIVITY_LIKE) { - $tpl = get_markup_template('diaspora_like.tpl'); - $like = true; - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); -// $target_type = (strpos($parent['type'], 'comment') ? 'Comment' : 'Post'); -// $positive = (($item['deleted']) ? 'false' : 'true'); - $positive = 'true'; - - if(($item['deleted'])) - logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction'); - } - else { - $tpl = get_markup_template('diaspora_comment.tpl'); - $like = false; - } - - $text = html_entity_decode(bb2diaspora($item['body'])); - - // sign it - - if($like) - $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; - else - $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; - - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($myaddr) - )); - - logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)); - - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); -} - - -function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { - - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); -// $theiraddr = $contact['addr']; - - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } - else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; - - $like = false; - $relay_retract = false; - $sql_sign_id = 'iid'; - if( $item['deleted']) { - $relay_retract = true; - - $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - - $sql_sign_id = 'retract_iid'; - $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); - } - elseif($item['verb'] === ACTIVITY_LIKE) { - $like = true; - - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); -// $positive = (($item['deleted']) ? 'false' : 'true'); - $positive = 'true'; - - $tpl = get_markup_template('diaspora_like_relay.tpl'); - } - else { // item is a comment - $tpl = get_markup_template('diaspora_comment_relay.tpl'); - } - - - // fetch the original signature if the relayable was created by a Diaspora - // or DFRN user. Relayables for other networks are not supported. - - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1", - intval($item['id']) - ); - if(count($r)) { - $orig_sign = $r[0]; - $signed_text = $orig_sign['signed_text']; - $authorsig = $orig_sign['signature']; - $handle = $orig_sign['signer']; - - // Split the signed text - $signed_parts = explode(";", $signed_text); - - // Remove the parent guid - array_shift($signed_parts); - - // Remove the comment guid - array_shift($signed_parts); - - // Remove the handle - array_pop($signed_parts); - - // Glue the parts together - $text = implode(";", $signed_parts); - } - else { - // This part is meant for cases where we don't have the signatur. (Which shouldn't happen with posts from Diaspora and Friendica) - // This means that the comment won't be accepted by newer Diaspora servers - - $body = $item['body']; - $text = html_entity_decode(bb2diaspora($body)); - - $handle = diaspora_handle_from_contact($item['contact-id']); - if(! $handle) - return; - - if($relay_retract) - $signed_text = $item['guid'] . ';' . $target_type; - elseif($like) - $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle; - else - $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle; - - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - } - - // Sign the relayable with the top-level owner's signature - $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$parentsig' => xmlify($parentauthorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($handle) - )); - - logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)); - - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); - -} - - - -function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - // Check whether the retraction is for a top-level post or whether it's a relayable - if( $item['uri'] !== $item['parent-uri'] ) { - - $tpl = get_markup_template('diaspora_relay_retraction.tpl'); - $target_type = (($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - } - else { - - $tpl = get_markup_template('diaspora_signed_retract.tpl'); - $target_type = 'StatusMessage'; - } - - $signed_text = $item['guid'] . ';' . $target_type; - - $msg = replace_macros($tpl, array( - '$guid' => xmlify($item['guid']), - '$type' => xmlify($target_type), - '$handle' => xmlify($myaddr), - '$signature' => xmlify(base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))) - )); - - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)); - - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); -} - -function diaspora_send_mail($item,$owner,$contact) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - $r = q("select * from conv where id = %d and uid = %d limit 1", - intval($item['convid']), - intval($item['uid']) - ); - - if(! count($r)) { - logger('diaspora_send_mail: conversation not found.'); - return; - } - $cnv = $r[0]; - - $conv = array( - 'guid' => xmlify($cnv['guid']), - 'subject' => xmlify($cnv['subject']), - 'created_at' => xmlify(datetime_convert('UTC','UTC',$cnv['created'],'Y-m-d H:i:s \U\T\C')), - 'diaspora_handle' => xmlify($cnv['creator']), - 'participant_handles' => xmlify($cnv['recips']) - ); - - $body = bb2diaspora($item['body']); - $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C'); - - $signed_text = $item['guid'] . ';' . $cnv['guid'] . ';' . $body . ';' - . $created . ';' . $myaddr . ';' . $cnv['guid']; - - $sig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = array( - 'guid' => xmlify($item['guid']), - 'parent_guid' => xmlify($cnv['guid']), - 'parent_author_signature' => xmlify($sig), - 'author_signature' => xmlify($sig), - 'text' => xmlify($body), - 'created_at' => xmlify($created), - 'diaspora_handle' => xmlify($myaddr), - 'conversation_guid' => xmlify($cnv['guid']) - ); - - if($item['reply']) { - $tpl = get_markup_template('diaspora_message.tpl'); - $xmsg = replace_macros($tpl, array('$msg' => $msg)); - } - else { - $conv['messages'] = array($msg); - $tpl = get_markup_template('diaspora_conversation.tpl'); - $xmsg = replace_macros($tpl, array('$conv' => $conv)); - } - - logger('diaspora_conversation: ' . print_r($xmsg,true), LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false)); - - return(diaspora_transmit($owner,$contact,$slap,false,false,$item['guid'])); - - -} - -function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false,$guid = "") { - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - return 200; - } - - $a = get_app(); - $logid = random_string(4); - $dest_url = (($public_batch) ? $contact['batch'] : $contact['notify']); - if(! $dest_url) { - logger('diaspora_transmit: no url for contact: ' . $contact['id'] . ' batch mode =' . $public_batch); - return 0; - } - - logger('diaspora_transmit: '.$logid.'-'.$guid.' '.$dest_url); - - if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) { - $return_code = 0; - } - else { - if (!intval(get_config('system','diaspora_test'))) { - post_url($dest_url . '/', $slap); - $return_code = $a->get_curl_code(); - } else { - logger('diaspora_transmit: test_mode'); - return 200; - } - } - - logger('diaspora_transmit: '.$logid.'-'.$guid.' returns: '.$return_code); - - if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) { - logger('diaspora_transmit: queue message'); - - $r = q("SELECT id from queue where cid = %d and network = '%s' and content = '%s' and batch = %d limit 1", - intval($contact['id']), - dbesc(NETWORK_DIASPORA), - dbesc($slap), - intval($public_batch) - ); - if(count($r)) { - logger('diaspora_transmit: add_to_queue ignored - identical item already in queue'); - } - else { - // queue message for redelivery - add_to_queue($contact['id'],NETWORK_DIASPORA,$slap,$public_batch); - } - } - - - return(($return_code) ? $return_code : (-1)); -} - -function diaspora_fetch_relay() { - - $serverdata = get_config("system", "relay_server"); - if ($serverdata == "") - return array(); - - $relay = array(); - - $servers = explode(",", $serverdata); - - foreach($servers AS $server) { - $server = trim($server); - $batch = $server."/receive/public"; - - $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); - - if (!$relais) { - $addr = "relay@".str_replace("http://", "", normalise_link($server)); - - $r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`) - VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')", - datetime_convert(), - dbesc($addr), - dbesc($addr), - dbesc($server), - dbesc(normalise_link($server)), - dbesc($batch), - dbesc(NETWORK_DIASPORA), - intval(CONTACT_IS_FOLLOWER), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert()) - ); - - $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); - if ($relais) - $relay[] = $relais[0]; - } else - $relay[] = $relais[0]; - } - - return $relay; + $message = array("diaspora_handle" => $handle, + "first_name" => $first, + "last_name" => $last, + "image_url" => $large, + "image_url_medium" => $medium, + "image_url_small" => $small, + "birthday" => $dob, + "gender" => $profile['gender'], + "bio" => $about, + "location" => $location, + "searchable" => $searchable, + "tag_string" => $tags); + + foreach($recips as $recip) + self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); + } } +?> diff --git a/include/diaspora2.php b/include/diaspora2.php deleted file mode 100644 index 75cedeccd1..0000000000 --- a/include/diaspora2.php +++ /dev/null @@ -1,2553 +0,0 @@ -<?php -/** - * @file include/diaspora.php - * @brief The implementation of the diaspora protocol - */ - -require_once("include/items.php"); -require_once("include/bb2diaspora.php"); -require_once("include/Scrape.php"); -require_once("include/Contact.php"); -require_once("include/Photo.php"); -require_once("include/socgraph.php"); -require_once("include/group.php"); -require_once("include/xml.php"); -require_once("include/datetime.php"); - -/** - * @brief This class contain functions to create and send Diaspora XML files - * - */ -class diaspora { - - public static function relay_list() { - - $serverdata = get_config("system", "relay_server"); - if ($serverdata == "") - return array(); - - $relay = array(); - - $servers = explode(",", $serverdata); - - foreach($servers AS $server) { - $server = trim($server); - $batch = $server."/receive/public"; - - $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); - - if (!$relais) { - $addr = "relay@".str_replace("http://", "", normalise_link($server)); - - $r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`) - VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')", - datetime_convert(), - dbesc($addr), - dbesc($addr), - dbesc($server), - dbesc(normalise_link($server)), - dbesc($batch), - dbesc(NETWORK_DIASPORA), - intval(CONTACT_IS_FOLLOWER), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert()) - ); - - $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); - if ($relais) - $relay[] = $relais[0]; - } else - $relay[] = $relais[0]; - } - - return $relay; - } - - function repair_signature($signature, $handle = "", $level = 1) { - - if ($signature == "") - return ($signature); - - if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) { - $signature = base64_decode($signature); - logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG); - - // Do a recursive call to be able to fix even multiple levels - if ($level < 10) - $signature = self::repair_signature($signature, $handle, ++$level); - } - - return($signature); - } - - /** - * @brief: Decodes incoming Diaspora message - * - * @param array $importer from user table - * @param string $xml urldecoded Diaspora salmon - * - * @return array - * 'message' -> decoded Diaspora XML message - * 'author' -> author diaspora handle - * 'key' -> author public key (converted to pkcs#8) - */ - function decode($importer, $xml) { - - $public = false; - $basedom = parse_xml_string($xml); - - if (!is_object($basedom)) - return false; - - $children = $basedom->children('https://joindiaspora.com/protocol'); - - if($children->header) { - $public = true; - $author_link = str_replace('acct:','',$children->header->author_id); - } else { - - $encrypted_header = json_decode(base64_decode($children->encrypted_header)); - - $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); - $ciphertext = base64_decode($encrypted_header->ciphertext); - - $outer_key_bundle = ''; - openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); - - $j_outer_key_bundle = json_decode($outer_key_bundle); - - $outer_iv = base64_decode($j_outer_key_bundle->iv); - $outer_key = base64_decode($j_outer_key_bundle->key); - - $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); - - - $decrypted = pkcs5_unpad($decrypted); - - /** - * $decrypted now contains something like - * - * <decrypted_header> - * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv> - * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key> - * <author_id>galaxor@diaspora.priateship.org</author_id> - * </decrypted_header> - */ - - logger('decrypted: '.$decrypted, LOGGER_DEBUG); - $idom = parse_xml_string($decrypted,false); - - $inner_iv = base64_decode($idom->iv); - $inner_aes_key = base64_decode($idom->aes_key); - - $author_link = str_replace('acct:','',$idom->author_id); - } - - $dom = $basedom->children(NAMESPACE_SALMON_ME); - - // figure out where in the DOM tree our data is hiding - - if($dom->provenance->data) - $base = $dom->provenance; - elseif($dom->env->data) - $base = $dom->env; - elseif($dom->data) - $base = $dom; - - if (!$base) { - logger('unable to locate salmon data in xml'); - http_status_exit(400); - } - - - // Stash the signature away for now. We have to find their key or it won't be good for anything. - $signature = base64url_decode($base->sig); - - // unpack the data - - // strip whitespace so our data element will return to one big base64 blob - $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); - - - // stash away some other stuff for later - - $type = $base->data[0]->attributes()->type[0]; - $keyhash = $base->sig[0]->attributes()->keyhash[0]; - $encoding = $base->encoding; - $alg = $base->alg; - - - $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg); - - - // decode the data - $data = base64url_decode($data); - - - if($public) - $inner_decrypted = $data; - else { - - // Decode the encrypted blob - - $inner_encrypted = base64_decode($data); - $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); - $inner_decrypted = pkcs5_unpad($inner_decrypted); - } - - if (!$author_link) { - logger('Could not retrieve author URI.'); - http_status_exit(400); - } - // Once we have the author URI, go to the web and try to find their public key - // (first this will look it up locally if it is in the fcontact cache) - // This will also convert diaspora public key from pkcs#1 to pkcs#8 - - logger('Fetching key for '.$author_link); - $key = self::key($author_link); - - if (!$key) { - logger('Could not retrieve author key.'); - http_status_exit(400); - } - - $verify = rsa_verify($signed_data,$signature,$key); - - if (!$verify) { - logger('Message did not verify. Discarding.'); - http_status_exit(400); - } - - logger('Message verified.'); - - return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); - - } - - - /** - * @brief Dispatches public messages and find the fitting receivers - * - * @param array $msg The post that will be dispatched - * - * @return bool Was the message accepted? - */ - public static function dispatch_public($msg) { - - $enabled = intval(get_config("system", "diaspora_enabled")); - if (!$enabled) { - logger("diaspora is disabled"); - return false; - } - - // Use a dummy importer to import the data for the public copy - $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); - $item_id = self::dispatch($importer,$msg); - - // 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') - AND NOT `account_expired` AND NOT `account_removed`", - dbesc(NETWORK_DIASPORA), - dbesc($msg["author"]) - ); - if($r) { - foreach($r as $rr) { - logger("delivering to: ".$rr["username"]); - self::dispatch($rr,$msg); - } - } else - logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); - - return $item_id; - } - - /** - * @brief Dispatches the different message types to the different functions - * - * @param array $importer Array of the importer user - * @param array $msg The post that will be dispatched - * - * @return bool Was the message accepted? - */ - public static function dispatch($importer, $msg) { - - // 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 (!diaspora::valid_posting($msg, $fields)) { - logger("Invalid posting"); - return false; - } - - $type = $fields->getName(); - - logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG); - - switch ($type) { - case "account_deletion": - return self::receive_account_deletion($importer, $fields); - - case "comment": - return self::receive_comment($importer, $sender, $fields); - - case "conversation": - return self::receive_conversation($importer, $msg, $fields); - - case "like": - return self::receive_like($importer, $sender, $fields); - - case "message": - return self::receive_message($importer, $fields); - - case "participation": // Not implemented - return self::receive_participation($importer, $fields); - - case "photo": // Not implemented - return self::receive_photo($importer, $fields); - - case "poll_participation": // Not implemented - return self::receive_poll_participation($importer, $fields); - - case "profile": - return self::receive_profile($importer, $fields); - - case "request": - return self::receive_request($importer, $fields); - - case "reshare": - return self::receive_reshare($importer, $fields); - - case "retraction": - return self::receive_retraction($importer, $sender, $fields); - - case "status_message": - return self::receive_status_message($importer, $fields); - - default: - logger("Unknown message type ".$type); - return false; - } - - return true; - } - - /** - * @brief Checks if a posting is valid and fetches the data fields. - * - * This function does not only check the signature. - * 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? - */ - private function valid_posting($msg, &$fields) { - - $data = parse_xml_string($msg["message"], false); - - if (!is_object($data)) - return false; - - $first_child = $data->getName(); - - // Is this the new or the old version? - if ($data->getName() == "XML") { - $oldXML = true; - foreach ($data->post->children() as $child) - $element = $child; - } else { - $oldXML = false; - $element = $data; - } - - $type = $element->getName(); - $orig_type = $type; - - // All retractions are handled identically from now on. - // In the new version there will only be "retraction". - if (in_array($type, array("signed_retraction", "relayable_retraction"))) - $type = "retraction"; - - $fields = new SimpleXMLElement("<".$type."/>"); - - $signed_data = ""; - - foreach ($element->children() AS $fieldname => $entry) { - if ($oldXML) { - // Translation for the old XML structure - 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") - $fieldname = "author"; - - if ($fieldname == "recipient_handle") - $fieldname = "recipient"; - - if ($fieldname == "root_diaspora_id") - $fieldname = "root_author"; - - if ($type == "retraction") { - if ($fieldname == "post_guid") - $fieldname = "target_guid"; - - if ($fieldname == "type") - $fieldname = "target_type"; - } - } - - if ($fieldname == "author_signature") - $author_signature = base64_decode($entry); - elseif ($fieldname == "parent_author_signature") - $parent_author_signature = base64_decode($entry); - elseif ($fieldname != "target_author_signature") { - if ($signed_data != "") { - $signed_data .= ";"; - $signed_data_parent .= ";"; - } - - $signed_data .= $entry; - } - if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR - ($orig_type == "relayable_retraction")) - xml::copy($entry, $fields, $fieldname); - } - - // This is something that shouldn't happen at all. - if (in_array($type, array("status_message", "reshare", "profile"))) - if ($msg["author"] != $fields->author) { - logger("Message handle is not the same as envelope sender. Quitting this message."); - return false; - } - - // Only some message types have signatures. So we quit here for the other types. - if (!in_array($type, array("comment", "message", "like"))) - return true; - - // No author_signature? This is a must, so we quit. - if (!isset($author_signature)) - return false; - - if (isset($parent_author_signature)) { - $key = self::key($msg["author"]); - - if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) - return false; - } - - $key = self::key($fields->author); - - return rsa_verify($signed_data, $author_signature, $key, "sha256"); - } - - /** - * @brief Fetches the public key for a given handle - * - * @param string $handle The handle - * - * @return string The public key - */ - private function key($handle) { - $handle = strval($handle); - - logger("Fetching diaspora key for: ".$handle); - - $r = self::person_by_handle($handle); - if($r) - return $r["pubkey"]; - - return ""; - } - - /** - * @brief Fetches data for a given handle - * - * @param string $handle The handle - * - * @return array the queried data - */ - private function person_by_handle($handle) { - - $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", - dbesc(NETWORK_DIASPORA), - dbesc($handle) - ); - if ($r) { - $person = $r[0]; - logger("In cache ".print_r($r,true), LOGGER_DEBUG); - - // update record occasionally so it doesn't get stale - $d = strtotime($person["updated"]." +00:00"); - if ($d < strtotime("now - 14 days")) - $update = true; - } - - if (!$person OR $update) { - logger("create or refresh", LOGGER_DEBUG); - $r = probe_url($handle, PROBE_DIASPORA); - - // Note that Friendica contacts will return a "Diaspora person" - // if Diaspora connectivity is enabled on their server - if ($r AND ($r["network"] === NETWORK_DIASPORA)) { - self::add_fcontact($r, $update); - $person = $r; - } - } - return $person; - } - - /** - * @brief Updates the fcontact table - * - * @param array $arr The fcontact data - * @param bool $update Update or insert? - * - * @return string The id of the fcontact entry - */ - private function add_fcontact($arr, $update = false) { - /// @todo Remove this function from include/network.php - - if($update) { - $r = q("UPDATE `fcontact` SET - `name` = '%s', - `photo` = '%s', - `request` = '%s', - `nick` = '%s', - `addr` = '%s', - `batch` = '%s', - `notify` = '%s', - `poll` = '%s', - `confirm` = '%s', - `alias` = '%s', - `pubkey` = '%s', - `updated` = '%s' - WHERE `url` = '%s' AND `network` = '%s'", - dbesc($arr["name"]), - dbesc($arr["photo"]), - dbesc($arr["request"]), - dbesc($arr["nick"]), - dbesc($arr["addr"]), - dbesc($arr["batch"]), - dbesc($arr["notify"]), - dbesc($arr["poll"]), - dbesc($arr["confirm"]), - dbesc($arr["alias"]), - dbesc($arr["pubkey"]), - dbesc(datetime_convert()), - dbesc($arr["url"]), - dbesc($arr["network"]) - ); - } else { - $r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`, - `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`) - VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", - dbesc($arr["url"]), - dbesc($arr["name"]), - dbesc($arr["photo"]), - dbesc($arr["request"]), - dbesc($arr["nick"]), - dbesc($arr["addr"]), - dbesc($arr["batch"]), - dbesc($arr["notify"]), - dbesc($arr["poll"]), - dbesc($arr["confirm"]), - dbesc($arr["network"]), - dbesc($arr["alias"]), - dbesc($arr["pubkey"]), - dbesc(datetime_convert()) - ); - } - - return $r; - } - - public static function handle_from_contact($contact_id) { - $handle = False; - - logger("contact id is ".$contact_id, LOGGER_DEBUG); - - $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", - intval($contact_id) - ); - if($r) { - $contact = $r[0]; - - logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); - - if($contact['addr'] != "") - $handle = $contact['addr']; - elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { - $baseurl_start = strpos($contact['url'],'://') + 3; - $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle - $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); - $handle = $contact['nick'].'@'.$baseurl; - } - } - - return $handle; - } - - private function contact_by_handle($uid, $handle) { - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", - intval($uid), - dbesc($handle) - ); - - if ($r) - return $r[0]; - - $handle_parts = explode("@", $handle); - $nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0]; - $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1", - dbesc(NETWORK_DFRN), - intval($uid), - dbesc($nurl_sql) - ); - if($r) - return $r[0]; - - return false; - } - - private function post_allow($importer, $contact, $is_comment = false) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - // Normally this should have handled by getting a request - but this could get lost - if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { - q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", - intval(CONTACT_IS_FRIEND), - intval($contact["id"]), - intval($importer["uid"]) - ); - $contact["rel"] = CONTACT_IS_FRIEND; - logger("defining user ".$contact["nick"]." as friend"); - } - - if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"])) - return false; - if($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) - return true; - - return false; - } - - private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { - $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 (!self::post_allow($importer, $contact, $is_comment)) { - logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]); - return false; - } - return $contact; - } - - private function message_exists($uid, $guid) { - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), - dbesc($guid) - ); - - if($r) { - logger("message ".$guid." already exists for user ".$uid); - return true; - } - - return false; - } - - private function fetch_guid($item) { - preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", - function ($match) use ($item){ - return(self::fetch_guid_sub($match, $item)); - },$item["body"]); - } - - private function fetch_guid_sub($match, $item) { - if (!self::store_by_guid($match[1], $item["author-link"])) - self::store_by_guid($match[1], $item["owner-link"]); - } - - private function store_by_guid($guid, $server, $uid = 0) { - $serverparts = parse_url($server); - $server = $serverparts["scheme"]."://".$serverparts["host"]; - - logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); - - $msg = self::message($guid, $server); - - if (!$msg) - return false; - - logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); - - // Now call the dispatcher - return self::dispatch_public($msg); - } - - private function message($guid, $server, $level = 0) { - - if ($level > 5) - return false; - - // This will work for Diaspora and newer Friendica servers - $source_url = $server."/p/".$guid.".xml"; - $x = fetch_url($source_url); - if(!$x) - return false; - - $source_xml = parse_xml_string($x, false); - - if (!is_object($source_xml)) - return false; - - if ($source_xml->post->reshare) { - // Reshare of a reshare - old Diaspora version - return self::message($source_xml->post->reshare->root_guid, $server, ++$level); - } elseif ($source_xml->getName() == "reshare") { - // Reshare of a reshare - new Diaspora version - return self::message($source_xml->root_guid, $server, ++$level); - } - - $author = ""; - - // Fetch the author - for the old and the new Diaspora version - if ($source_xml->post->status_message->diaspora_handle) - $author = (string)$source_xml->post->status_message->diaspora_handle; - elseif ($source_xml->author AND ($source_xml->getName() == "status_message")) - $author = (string)$source_xml->author; - - // If this isn't a "status_message" then quit - if (!$author) - return false; - - $msg = array("message" => $x, "author" => $author); - - $msg["key"] = self::key($msg["author"]); - - return $msg; - } - - private function parent_item($uid, $guid, $author, $contact) { - $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, - `author-name`, `author-link`, `author-avatar`, - `owner-name`, `owner-link`, `owner-avatar` - FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), dbesc($guid)); - - if(!$r) { - $result = self::store_by_guid($guid, $contact["url"], $uid); - - if (!$result) { - $person = self::person_by_handle($author); - $result = self::store_by_guid($guid, $person["url"], $uid); - } - - if ($result) { - logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG); - - $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, - `author-name`, `author-link`, `author-avatar`, - `owner-name`, `owner-link`, `owner-avatar` - FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), dbesc($guid)); - } - } - - if (!$r) { - logger("parent item not found: parent: ".$guid." - user: ".$uid); - return false; - } else { - logger("parent item found: parent: ".$guid." - user: ".$uid); - return $r[0]; - } - } - - private function author_contact_by_url($contact, $person, $uid) { - - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person["url"])), intval($uid)); - if ($r) { - $cid = $r[0]["id"]; - $network = $r[0]["network"]; - } else { - $cid = $contact["id"]; - $network = NETWORK_DIASPORA; - } - - return (array("cid" => $cid, "network" => $network)); - } - - public static function is_redmatrix($url) { - return(strstr($url, "/channel/")); - } - - private function plink($addr, $guid) { - $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); - - // Fallback - if (!$r) - return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; - - // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table - // So we try another way as well. - $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); - if ($s) - $r[0]["network"] = $s[0]["network"]; - - if ($r[0]["network"] == NETWORK_DFRN) - return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); - - if (self::is_redmatrix($r[0]["url"])) - return $r[0]["url"]."/?f=&mid=".$guid; - - return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; - } - - private function receive_account_deletion($importer, $data) { - $author = notags(unxmlify($data->author)); - - $contact = self::contact_by_handle($importer["uid"], $author); - if (!$contact) { - logger("cannot find contact for author: ".$author); - return false; - } - - // We now remove the contact - contact_remove($contact["id"]); - return true; - } - - private function receive_comment($importer, $sender, $data) { - $guid = notags(unxmlify($data->guid)); - $parent_guid = notags(unxmlify($data->parent_guid)); - $text = unxmlify($data->text); - $author = notags(unxmlify($data->author)); - - $contact = self::allowed_contact_by_handle($importer, $sender, true); - if (!$contact) - return false; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); - if (!$parent_item) - return false; - - $person = self::person_by_handle($author); - if (!is_array($person)) { - logger("unable to find author details"); - return false; - } - - // Fetch the contact id - if we know this contact - $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); - - $datarray = array(); - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $author_contact["cid"]; - $datarray["network"] = $author_contact["network"]; - - $datarray["author-name"] = $person["name"]; - $datarray["author-link"] = $person["url"]; - $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); - - $datarray["owner-name"] = $contact["name"]; - $datarray["owner-link"] = $contact["url"]; - $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["guid"] = $guid; - $datarray["uri"] = $author.":".$guid; - - $datarray["type"] = "remote-comment"; - $datarray["verb"] = ACTIVITY_POST; - $datarray["gravity"] = GRAVITY_COMMENT; - $datarray["parent-uri"] = $parent_item["uri"]; - - $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; - $datarray["object"] = json_encode($data); - - $datarray["body"] = diaspora2bb($text); - - self::fetch_guid($datarray); - - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - // If we are the origin of the parent we store the original data and notify our followers - if($message_id AND $parent_item["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", - intval($message_id), - dbesc(json_encode($data)) - ); - - // notify others - proc_run("php", "include/notifier.php", "comment-import", $message_id); - } - - return $message_id; - } - - private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { - $guid = notags(unxmlify($data->guid)); - $subject = notags(unxmlify($data->subject)); - $author = notags(unxmlify($data->author)); - - $reply = 0; - - $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 - if ($mesg->author) - $msg_author = notags(unxmlify($mesg->author)); - elseif ($mesg->diaspora_handle) - $msg_author = notags(unxmlify($mesg->diaspora_handle)); - else - return false; - - $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); - - if($msg_conversation_guid != $guid) { - logger("message conversation guid does not belong to the current conversation."); - return false; - } - - $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; - - $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) - ); - if($r) { - logger("duplicate message already delivered.", LOGGER_DEBUG); - return false; - } - - q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) - VALUES (%d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer["uid"]), - dbesc($msg_guid), - intval($conversation["id"]), - dbesc($person["name"]), - dbesc($person["photo"]), - dbesc($person["url"]), - intval($contact["id"]), - dbesc($subject), - dbesc($body), - 0, - 0, - dbesc($message_uri), - dbesc($author.":".$guid), - dbesc($msg_created_at) - ); - - q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", - dbesc(datetime_convert()), - intval($conversation["id"]) - ); - - notification(array( - "type" => NOTIFY_MAIL, - "notify_flags" => $importer["notify-flags"], - "language" => $importer["language"], - "to_name" => $importer["username"], - "to_email" => $importer["email"], - "uid" =>$importer["uid"], - "item" => array("subject" => $subject, "body" => $body), - "source_name" => $person["name"], - "source_link" => $person["url"], - "source_photo" => $person["thumb"], - "verb" => ACTIVITY_POST, - "otype" => "mail" - )); - } - - private function receive_conversation($importer, $msg, $data) { - $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; - - if (!count($messages)) { - logger("empty conversation"); - return false; - } - - $contact = self::allowed_contact_by_handle($importer, $msg["author"], true); - if (!$contact) - return false; - - $conversation = null; - - $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - if($c) - $conversation = $c[0]; - else { - $r = q("INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`) - VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s')", - intval($importer["uid"]), - dbesc($guid), - dbesc($author), - dbesc(datetime_convert("UTC", "UTC", $created_at)), - dbesc(datetime_convert()), - dbesc($subject), - dbesc($participants) - ); - if($r) - $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - - if($c) - $conversation = $c[0]; - } - if (!$conversation) { - logger("unable to create conversation."); - return; - } - - foreach($messages as $mesg) - self::receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation); - - return true; - } - - private function construct_like_body($contact, $parent_item, $guid) { - $bodyverb = t('%1$s likes %2$s\'s %3$s'); - - $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; - $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]"; - $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]"; - - return sprintf($bodyverb, $ulink, $alink, $plink); - } - - private function construct_like_object($importer, $parent_item) { - $objtype = ACTIVITY_OBJ_NOTE; - $link = '<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'; - $parent_body = $parent_item["body"]; - - $xmldata = array("object" => array("type" => $objtype, - "local" => "1", - "id" => $parent_item["uri"], - "link" => $link, - "title" => "", - "content" => $parent_body)); - - return xml::from_array($xmldata, $xml, true); - } - - private 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)); - - // 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. - if (!in_array($parent_type, array("Post", "Comment"))) - return false; - - $contact = self::allowed_contact_by_handle($importer, $sender, true); - if (!$contact) - return false; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); - if (!$parent_item) - return false; - - $person = self::person_by_handle($author); - if (!is_array($person)) { - logger("unable to find author details"); - return false; - } - - // Fetch the contact id - if we know this contact - $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); - - // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora - // We would accept this anyhow. - if ($positive === "true") - $verb = ACTIVITY_LIKE; - else - $verb = ACTIVITY_DISLIKE; - - $datarray = array(); - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $author_contact["cid"]; - $datarray["network"] = $author_contact["network"]; - - $datarray["author-name"] = $person["name"]; - $datarray["author-link"] = $person["url"]; - $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); - - $datarray["owner-name"] = $contact["name"]; - $datarray["owner-link"] = $contact["url"]; - $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["guid"] = $guid; - $datarray["uri"] = $author.":".$guid; - - $datarray["type"] = "activity"; - $datarray["verb"] = $verb; - $datarray["gravity"] = GRAVITY_LIKE; - $datarray["parent-uri"] = $parent_item["uri"]; - - $datarray["object-type"] = ACTIVITY_OBJ_NOTE; - $datarray["object"] = self::construct_like_object($importer, $parent_item); - - $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); - - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - // If we are the origin of the parent we store the original data and notify our followers - if($message_id AND $parent_item["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", - intval($message_id), - dbesc(json_encode($data)) - ); - - // notify others - proc_run("php", "include/notifier.php", "comment-import", $message_id); - } - - return $message_id; - } - - private function receive_message($importer, $data) { - $guid = notags(unxmlify($data->guid)); - $parent_guid = notags(unxmlify($data->parent_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) - return false; - - $conversation = null; - - $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($conversation_guid) - ); - if($c) - $conversation = $c[0]; - else { - logger("conversation not available."); - return false; - } - - $reply = 0; - - $body = diaspora2bb($text); - $message_uri = $author.":".$guid; - - $person = self::person_by_handle($author); - if (!$person) { - logger("unable to find author details"); - return false; - } - - $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($message_uri), - intval($importer["uid"]) - ); - if($r) { - logger("duplicate message already delivered.", LOGGER_DEBUG); - return false; - } - - q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) - VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer["uid"]), - dbesc($guid), - intval($conversation["id"]), - dbesc($person["name"]), - dbesc($person["photo"]), - dbesc($person["url"]), - intval($contact["id"]), - dbesc($conversation["subject"]), - dbesc($body), - 0, - 1, - dbesc($message_uri), - dbesc($author.":".$parent_guid), - dbesc($created_at) - ); - - q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", - dbesc(datetime_convert()), - intval($conversation["id"]) - ); - - return true; - } - - private function receive_participation($importer, $data) { - // I'm not sure if we can fully support this message type - return true; - } - - private function receive_photo($importer, $data) { - // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well - return true; - } - - private function receive_poll_participation($importer, $data) { - // We don't support polls by now - return true; - } - - private function receive_profile($importer, $data) { - $author = notags(unxmlify($data->author)); - - $contact = self::contact_by_handle($importer["uid"], $author); - if (!$contact) - return; - - $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); - $searchable = (unxmlify($data->searchable) == "true"); - $nsfw = (unxmlify($data->nsfw) == "true"); - $tags = unxmlify($data->tag_string); - - $tags = explode("#", $tags); - - $keywords = array(); - foreach ($tags as $tag) { - $tag = trim(strtolower($tag)); - if ($tag != "") - $keywords[] = $tag; - } - - $keywords = implode(", ", $keywords); - - $handle_parts = explode("@", $author); - $nick = $handle_parts[0]; - - if($name === "") - $name = $handle_parts[0]; - - if( preg_match("|^https?://|", $image_url) === 0) - $image_url = "http://".$handle_parts[1].$image_url; - - update_contact_avatar($image_url, $importer["uid"], $contact["id"]); - - // Generic birthday. We don't know the timezone. The year is irrelevant. - - $birthday = str_replace("1000", "1901", $birthday); - - if ($birthday != "") - $birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d"); - - // this is to prevent multiple birthday notifications in a single year - // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year - - if(substr($birthday,5) === substr($contact["bd"],5)) - $birthday = $contact["bd"]; - - $r = q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', `name-date` = '%s', `bd` = '%s', - `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d", - dbesc($name), - dbesc($nick), - dbesc($author), - dbesc(datetime_convert()), - dbesc($birthday), - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - intval($contact["id"]), - intval($importer["uid"]) - ); - - if ($searchable) { - poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", - datetime_convert(), 2, $contact["id"], $importer["uid"]); - } - - $gcontact = array("url" => $contact["url"], "network" => NETWORK_DIASPORA, "generation" => 2, - "photo" => $image_url, "name" => $name, "location" => $location, - "about" => $about, "birthday" => $birthday, "gender" => $gender, - "addr" => $author, "nick" => $nick, "keywords" => $keywords, - "hide" => !$searchable, "nsfw" => $nsfw); - - update_gcontact($gcontact); - - logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG); - - return true; - } - - private function receive_request_make_friend($importer, $contact) { - - $a = get_app(); - - if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { - q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", - intval(CONTACT_IS_FRIEND), - intval($contact["id"]), - intval($importer["uid"]) - ); - } - // send notification - - $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", - intval($importer["uid"]) - ); - - if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) { - - $self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", - intval($importer["uid"]) - ); - - // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array - - if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) { - - $arr = array(); - $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]); - $arr["uid"] = $importer["uid"]; - $arr["contact-id"] = $self[0]["id"]; - $arr["wall"] = 1; - $arr["type"] = 'wall'; - $arr["gravity"] = 0; - $arr["origin"] = 1; - $arr["author-name"] = $arr["owner-name"] = $self[0]["name"]; - $arr["author-link"] = $arr["owner-link"] = $self[0]["url"]; - $arr["author-avatar"] = $arr["owner-avatar"] = $self[0]["thumb"]; - $arr["verb"] = ACTIVITY_FRIEND; - $arr["object-type"] = ACTIVITY_OBJ_PERSON; - - $A = "[url=".$self[0]["url"]."]".$self[0]["name"]."[/url]"; - $B = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; - $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]"; - $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto; - - $arr["object"] = "<object><type>".ACTIVITY_OBJ_PERSON."</type><title>".$contact["name"]."</title>" - ."<id>".$contact["url"]."/".$contact["name"]."</id>"; - $arr["object"] .= "<link>".xmlify('<link rel="alternate" type="text/html" href="'.$contact["url"].'" />'."\n"); - $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="'.$contact["thumb"].'" />'."\n"); - $arr["object"] .= "</link></object>\n"; - $arr["last-child"] = 1; - - $arr["allow_cid"] = $user[0]["allow_cid"]; - $arr["allow_gid"] = $user[0]["allow_gid"]; - $arr["deny_cid"] = $user[0]["deny_cid"]; - $arr["deny_gid"] = $user[0]["deny_gid"]; - - $i = item_store($arr); - if($i) - proc_run("php", "include/notifier.php", "activity", $i); - - } - - } - } - - private function receive_request($importer, $data) { - $author = unxmlify($data->author); - $recipient = unxmlify($data->recipient); - - if (!$author || !$recipient) - return; - - $contact = self::contact_by_handle($importer["uid"],$author); - - if($contact) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - - self::receive_request_make_friend($importer, $contact); - return true; - } - - $ret = self::person_by_handle($author); - - if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { - logger("Cannot resolve diaspora handle ".$author." for ".$recipient); - return false; - } - - $batch = (($ret["batch"]) ? $ret["batch"] : implode("/", array_slice(explode("/", $ret["url"]), 0, 3))."/receive/public"); - - $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) - VALUES (%d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d)", - intval($importer["uid"]), - dbesc($ret["network"]), - dbesc($ret["addr"]), - datetime_convert(), - dbesc($ret["url"]), - dbesc(normalise_link($ret["url"])), - dbesc($batch), - dbesc($ret["name"]), - dbesc($ret["nick"]), - dbesc($ret["photo"]), - dbesc($ret["pubkey"]), - dbesc($ret["notify"]), - dbesc($ret["poll"]), - 1, - 2 - ); - - // find the contact record we just created - - $contact_record = self::contact_by_handle($importer["uid"],$author); - - if (!$contact_record) { - logger("unable to locate newly created contact record."); - return; - } - - $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", - intval($importer["uid"]) - ); - - if($g && intval($g[0]["def_gid"])) - group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]); - - if($importer["page-flags"] == PAGE_NORMAL) { - - $hash = random_string().(string)time(); // Generate a confirm_key - - $ret = q("INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`) - VALUES (%d, %d, %d, %d, '%s', '%s', '%s')", - intval($importer["uid"]), - intval($contact_record["id"]), - 0, - 0, - dbesc(t("Sharing notification from Diaspora network")), - dbesc($hash), - dbesc(datetime_convert()) - ); - } else { - - // automatic friend approval - - update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]); - - // technically they are sharing with us (CONTACT_IS_SHARING), - // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX - // we are going to change the relationship and make them a follower. - - if($importer["page-flags"] == PAGE_FREELOVE) - $new_relation = CONTACT_IS_FRIEND; - else - $new_relation = CONTACT_IS_FOLLOWER; - - $r = q("UPDATE `contact` SET `rel` = %d, - `name-date` = '%s', - `uri-date` = '%s', - `blocked` = 0, - `pending` = 0, - `writable` = 1 - WHERE `id` = %d - ", - intval($new_relation), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($contact_record["id"]) - ); - - $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); - if($u) - $ret = self::send_share($u[0], $contact_record); - } - - return true; - } - - private function original_item($guid, $orig_author, $author) { - - // Do we already have this item? - $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, - `author-name`, `author-link`, `author-avatar` - FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", - dbesc($guid)); - - if($r) { - logger("reshared message ".$guid." already exists on system."); - - // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - if (self::is_reshare($r[0]["body"])) - $r = array(); - else - return $r[0]; - } - - if (!$r) { - $server = "https://".substr($orig_author, strpos($orig_author, "@") + 1); - logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); - $item_id = self::store_by_guid($guid, $server); - - if (!$item_id) { - $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1); - logger("2nd try: reshared message ".$guid." will be fetched from original server: ".$server); - $item_id = self::store_by_guid($guid, $server); - } - - // Deactivated by now since there is a risk that someone could manipulate postings through this method -/* if (!$item_id) { - $server = "https://".substr($author, strpos($author, "@") + 1); - logger("3rd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item_id = self::store_by_guid($guid, $server); - } - if (!$item_id) { - $server = "http://".substr($author, strpos($author, "@") + 1); - logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item_id = self::store_by_guid($guid, $server); - } -*/ - if ($item_id) { - $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, - `author-name`, `author-link`, `author-avatar` - FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", - intval($item_id)); - - if ($r) - return $r[0]; - - } - } - return false; - } - - private function receive_reshare($importer, $data) { - $root_author = notags(unxmlify($data->root_author)); - $root_guid = notags(unxmlify($data->root_guid)); - $guid = notags(unxmlify($data->guid)); - $author = notags(unxmlify($data->author)); - $public = notags(unxmlify($data->public)); - $created_at = notags(unxmlify($data->created_at)); - - $contact = self::allowed_contact_by_handle($importer, $author, false); - if (!$contact) - return false; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $original_item = self::original_item($root_guid, $root_author, $author); - if (!$original_item) - return false; - - $orig_url = App::get_baseurl()."/display/".$original_item["guid"]; - - $datarray = array(); - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $contact["id"]; - $datarray["network"] = NETWORK_DIASPORA; - - $datarray["author-name"] = $contact["name"]; - $datarray["author-link"] = $contact["url"]; - $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["owner-name"] = $datarray["author-name"]; - $datarray["owner-link"] = $datarray["author-link"]; - $datarray["owner-avatar"] = $datarray["author-avatar"]; - - $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; - - $datarray["verb"] = ACTIVITY_POST; - $datarray["gravity"] = GRAVITY_PARENT; - - $datarray["object"] = json_encode($data); - - $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], - $original_item["guid"], $original_item["created"], $orig_url); - $datarray["body"] = $prefix.$original_item["body"]."[/share]"; - - $datarray["tag"] = $original_item["tag"]; - $datarray["app"] = $original_item["app"]; - - $datarray["plink"] = self::plink($author, $guid); - $datarray["private"] = (($public == "false") ? 1 : 0); - $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - - $datarray["object-type"] = $original_item["object-type"]; - - self::fetch_guid($datarray); - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - return $message_id; - } - - private 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)); - - $person = self::person_by_handle($author); - if (!is_array($person)) { - logger("unable to find author detail for ".$author); - return false; - } - - $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) - return false; - - // Only delete it if the author really fits - if (!link_compare($r[0]["author-link"], $person["url"])) { - logger("Item author ".$r[0]["author-link"]." doesn't fit to expected contact ".$person["url"], LOGGER_DEBUG); - return false; - } - - // Check if the sender is the thread owner - $p = q("SELECT `id`, `author-link`, `origin` FROM `item` WHERE `id` = %d", - intval($r[0]["parent"])); - - // Only delete it if the parent author really fits - if (!link_compare($p[0]["author-link"], $contact["url"]) AND !link_compare($r[0]["author-link"], $contact["url"])) { - logger("Thread author ".$p[0]["author-link"]." and item author ".$r[0]["author-link"]." don't fit to expected contact ".$contact["url"], LOGGER_DEBUG); - return false; - } - - // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case - q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d", - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($r[0]["id"]) - ); - delete_thread($r[0]["id"], $r[0]["parent-uri"]); - - logger("Deleted target ".$target_guid." (".$r[0]["id"].") from user ".$importer["uid"]." parent: ".$p[0]["id"], LOGGER_DEBUG); - - // Now check if the retraction needs to be relayed by us - if($p[0]["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", - intval($r[0]["id"]), - dbesc(json_encode($data)) - ); - $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); - logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); - - // notify others - proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); - } - } - - private function receive_retraction($importer, $sender, $data) { - $target_type = notags(unxmlify($data->target_type)); - - $contact = self::contact_by_handle($importer["uid"], $sender); - if (!$contact) { - logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]); - return false; - } - - logger("Got retraction for ".$target_type.", sender ".$sender." and user ".$importer["uid"], LOGGER_DEBUG); - - switch ($target_type) { - case "Comment": - case "Like": - case "Post": // "Post" will be supported in a future version - case "Reshare": - case "StatusMessage": - return self::item_retraction($importer, $contact, $data);; - - case "Person": - /// @todo What should we do with an "unshare"? - // Removing the contact isn't correct since we still can read the public items - //contact_remove($contact["id"]); - return true; - - default: - logger("Unknown target type ".$target_type); - return false; - } - return true; - } - - private function receive_status_message($importer, $data) { - - $raw_message = unxmlify($data->raw_message); - $guid = notags(unxmlify($data->guid)); - $author = notags(unxmlify($data->author)); - $public = notags(unxmlify($data->public)); - $created_at = notags(unxmlify($data->created_at)); - $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; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $address = array(); - if ($data->location) - foreach ($data->location->children() AS $fieldname => $data) - $address[$fieldname] = notags(unxmlify($data)); - - $body = diaspora2bb($raw_message); - - $datarray = array(); - - if ($data->photo) { - foreach ($data->photo AS $photo) - $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; - - $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; - } else { - $datarray["object-type"] = ACTIVITY_OBJ_NOTE; - - // Add OEmbed and other information to the body - if (!self::is_redmatrix($contact["url"])) - $body = add_page_info_to_body($body, false, true); - } - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $contact["id"]; - $datarray["network"] = NETWORK_DIASPORA; - - $datarray["author-name"] = $contact["name"]; - $datarray["author-link"] = $contact["url"]; - $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["owner-name"] = $datarray["author-name"]; - $datarray["owner-link"] = $datarray["author-link"]; - $datarray["owner-avatar"] = $datarray["author-avatar"]; - - $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; - - $datarray["verb"] = ACTIVITY_POST; - $datarray["gravity"] = GRAVITY_PARENT; - - $datarray["object"] = json_encode($data); - - $datarray["body"] = $body; - - if ($provider_display_name != "") - $datarray["app"] = $provider_display_name; - - $datarray["plink"] = self::plink($author, $guid); - $datarray["private"] = (($public == "false") ? 1 : 0); - $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - - if (isset($address["address"])) - $datarray["location"] = $address["address"]; - - if (isset($address["lat"]) AND isset($address["lng"])) - $datarray["coord"] = $address["lat"]." ".$address["lng"]; - - self::fetch_guid($datarray); - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - return $message_id; - } - - /****************************************************************************************** - * Here are all the functions that are needed to transmit data with the Diaspora protocol * - ******************************************************************************************/ - - private function my_handle($me) { - if ($contact["addr"] != "") - return $contact["addr"]; - - // Normally we should have a filled "addr" field - but in the past this wasn't the case - // So - just in case - we build the the address here. - return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); - } - - private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { - - logger("Message: ".$msg, LOGGER_DATA); - - $handle = self::my_handle($user); - - $b64url_data = base64url_encode($msg); - - $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); - - $type = "application/xml"; - $encoding = "base64url"; - $alg = "RSA-SHA256"; - - $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - - $xmldata = array("diaspora" => array("header" => array("author_id" => $handle), - "me:env" => array("me:encoding" => "base64url", - "me:alg" => "RSA-SHA256", - "me:data" => $data, - "@attributes" => array("type" => "application/xml"), - "me:sig" => $sig))); - - $namespaces = array("" => "https://joindiaspora.com/protocol", - "me" => "http://salmon-protocol.org/ns/magic-env"); - - $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); - - logger("magic_env: ".$magic_env, LOGGER_DATA); - return $magic_env; - } - - private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { - - logger("Message: ".$msg, LOGGER_DATA); - - // without a public key nothing will work - - if (!$pubkey) { - logger("pubkey missing: contact id: ".$contact["id"]); - return false; - } - - $inner_aes_key = random_string(32); - $b_inner_aes_key = base64_encode($inner_aes_key); - $inner_iv = random_string(16); - $b_inner_iv = base64_encode($inner_iv); - - $outer_aes_key = random_string(32); - $b_outer_aes_key = base64_encode($outer_aes_key); - $outer_iv = random_string(16); - $b_outer_iv = base64_encode($outer_iv); - - $handle = self::my_handle($user); - - $padded_data = pkcs5_pad($msg,16); - $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); - - $b64_data = base64_encode($inner_encrypted); - - - $b64url_data = base64url_encode($b64_data); - $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); - - $type = "application/xml"; - $encoding = "base64url"; - $alg = "RSA-SHA256"; - - $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - - $xmldata = array("decrypted_header" => array("iv" => $b_inner_iv, - "aes_key" => $b_inner_aes_key, - "author_id" => $handle)); - - $decrypted_header = xml::from_array($xmldata, $xml, true); - $decrypted_header = pkcs5_pad($decrypted_header,16); - - $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); - - $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key)); - - $encrypted_outer_key_bundle = ""; - openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey); - - $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); - - logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA); - - $encrypted_header_json_object = json_encode(array("aes_key" => base64_encode($encrypted_outer_key_bundle), - "ciphertext" => base64_encode($ciphertext))); - $cipher_json = base64_encode($encrypted_header_json_object); - - $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, - "me:env" => array("me:encoding" => "base64url", - "me:alg" => "RSA-SHA256", - "me:data" => $data, - "@attributes" => array("type" => "application/xml"), - "me:sig" => $sig))); - - $namespaces = array("" => "https://joindiaspora.com/protocol", - "me" => "http://salmon-protocol.org/ns/magic-env"); - - $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); - - logger("magic_env: ".$magic_env, LOGGER_DATA); - return $magic_env; - } - - private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { - - if ($public) - $magic_env = self::build_public_message($msg,$user,$contact,$prvkey,$pubkey); - else - $magic_env = self::build_private_message($msg,$user,$contact,$prvkey,$pubkey); - - // The data that will be transmitted is double encoded via "urlencode", strange ... - $slap = "xml=".urlencode(urlencode($magic_env)); - return $slap; - } - - private function signature($owner, $message) { - $sigmsg = $message; - unset($sigmsg["author_signature"]); - unset($sigmsg["parent_author_signature"]); - - $signed_text = implode(";", $sigmsg); - - return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - } - - public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { - - $a = get_app(); - - $enabled = intval(get_config("system", "diaspora_enabled")); - if(!$enabled) - return 200; - - $logid = random_string(4); - $dest_url = (($public_batch) ? $contact["batch"] : $contact["notify"]); - if (!$dest_url) { - logger("no url for contact: ".$contact["id"]." batch mode =".$public_batch); - return 0; - } - - logger("transmit: ".$logid."-".$guid." ".$dest_url); - - if (!$queue_run && was_recently_delayed($contact["id"])) { - $return_code = 0; - } else { - if (!intval(get_config("system", "diaspora_test"))) { - post_url($dest_url."/", $slap); - $return_code = $a->get_curl_code(); - } else { - logger("test_mode"); - return 200; - } - } - - logger("transmit: ".$logid."-".$guid." returns: ".$return_code); - - if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) { - logger("queue message"); - - $r = q("SELECT `id` FROM `queue` WHERE `cid` = %d AND `network` = '%s' AND `content` = '%s' AND `batch` = %d LIMIT 1", - intval($contact["id"]), - dbesc(NETWORK_DIASPORA), - dbesc($slap), - intval($public_batch) - ); - if($r) { - logger("add_to_queue ignored - identical item already in queue"); - } else { - // queue message for redelivery - add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch); - } - } - - return(($return_code) ? $return_code : (-1)); - } - - - private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { - - $data = array("XML" => array("post" => array($type => $message))); - - $msg = xml::from_array($data, $xml); - - logger('message: '.$msg, LOGGER_DATA); - logger('send guid '.$guid, LOGGER_DEBUG); - - $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - - if ($spool) { - add_to_queue($contact['id'], NETWORK_DIASPORA, $slap, $public_batch); - return true; - } else - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; - } - - public static function send_share($owner,$contact) { - - $message = array("sender_handle" => self::my_handle($owner), - "recipient_handle" => $contact["addr"]); - - return self::build_and_transmit($owner, $contact, "request", $message); - } - - public static function send_unshare($owner,$contact) { - - $message = array("post_guid" => $owner["guid"], - "diaspora_handle" => self::my_handle($owner), - "type" => "Person"); - - return self::build_and_transmit($owner, $contact, "retraction", $message); - } - - public static function is_reshare($body) { - $body = trim($body); - - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(false); - - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(false); - - $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); - // Skip if there is no shared message in there - if ($body == $attributes) - return(false); - - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - if ($guid != "") { - $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", - dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); - if ($r) { - $ret= array(); - $ret["root_handle"] = self::handle_from_contact($r[0]["contact-id"]); - $ret["root_guid"] = $guid; - return($ret); - } - } - - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - $ret= array(); - - $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); - if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) - return(false); - - $link = ""; - preg_match("/link='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - preg_match('/link="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); - if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) - return(false); - return($ret); - } - - public static function send_status($item, $owner, $contact, $public_batch = false) { - - $myaddr = self::my_handle($owner); - - $public = (($item["private"]) ? "false" : "true"); - - $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); - - // Detect a share element and do a reshare - if (!$item['private'] AND ($ret = self::is_reshare($item["body"]))) { - $message = array("root_diaspora_id" => $ret["root_handle"], - "root_guid" => $ret["root_guid"], - "guid" => $item["guid"], - "diaspora_handle" => $myaddr, - "public" => $public, - "created_at" => $created, - "provider_display_name" => $item["app"]); - - $type = "reshare"; - } else { - $title = $item["title"]; - $body = $item["body"]; - - // convert to markdown - $body = html_entity_decode(bb2diaspora($body)); - - // Adding the title - if(strlen($title)) - $body = "## ".html_entity_decode($title)."\n\n".$body; - - if ($item["attach"]) { - $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); - if(cnt) { - $body .= "\n".t("Attachments:")."\n"; - foreach($matches as $mtch) - $body .= "[".$mtch[3]."](".$mtch[1].")\n"; - } - } - - $location = array(); - - if ($item["location"] != "") - $location["address"] = $item["location"]; - - if ($item["coord"] != "") { - $coord = explode(" ", $item["coord"]); - $location["lat"] = $coord[0]; - $location["lng"] = $coord[1]; - } - - $message = array("raw_message" => $body, - "location" => $location, - "guid" => $item["guid"], - "diaspora_handle" => $myaddr, - "public" => $public, - "created_at" => $created, - "provider_display_name" => $item["app"]); - - if (count($location) == 0) - unset($message["location"]); - - $type = "status_message"; - } - - return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); - } - - private function construct_like($item, $owner) { - - $myaddr = self::my_handle($owner); - - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", - dbesc($item["thr-parent"])); - if(!$p) - return false; - - $parent = $p[0]; - - $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); - $positive = "true"; - - return(array("positive" => $positive, - "guid" => $item["guid"], - "target_type" => $target_type, - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "diaspora_handle" => $myaddr)); - } - - private function construct_comment($item, $owner) { - - $myaddr = self::my_handle($owner); - - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($item["parent"]), - intval($item["parent"]) - ); - - if (!$p) - return false; - - $parent = $p[0]; - - $text = html_entity_decode(bb2diaspora($item["body"])); - - return(array("guid" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => "", - "text" => $text, - "diaspora_handle" => $myaddr)); - } - - public static function send_followup($item,$owner,$contact,$public_batch = false) { - - if($item['verb'] === ACTIVITY_LIKE) { - $message = self::construct_like($item, $owner); - $type = "like"; - } else { - $message = self::construct_comment($item, $owner); - $type = "comment"; - } - - if (!$message) - return false; - - $message["author_signature"] = self::signature($owner, $message); - - return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); - } - - private function message_from_signatur($item, $signature) { - - // Split the signed text - $signed_parts = explode(";", $signature['signed_text']); - - if ($item["deleted"]) - $message = array("parent_author_signature" => "", - "target_guid" => $signed_parts[0], - "target_type" => $signed_parts[1], - "sender_handle" => $signature['signer'], - "target_author_signature" => $signature['signature']); - elseif ($item['verb'] === ACTIVITY_LIKE) - $message = array("positive" => $signed_parts[0], - "guid" => $signed_parts[1], - "target_type" => $signed_parts[2], - "parent_guid" => $signed_parts[3], - "parent_author_signature" => "", - "author_signature" => $signature['signature'], - "diaspora_handle" => $signed_parts[4]); - else { - // Remove the comment guid - $guid = array_shift($signed_parts); - - // Remove the parent guid - $parent_guid = array_shift($signed_parts); - - // Remove the handle - $handle = array_pop($signed_parts); - - // Glue the parts together - $text = implode(";", $signed_parts); - - $message = array("guid" => $guid, - "parent_guid" => $parent_guid, - "parent_author_signature" => "", - "author_signature" => $signature['signature'], - "text" => implode(";", $signed_parts), - "diaspora_handle" => $handle); - } - return $message; - } - - public static function send_relay($item, $owner, $contact, $public_batch = false) { - - if ($item["deleted"]) { - $sql_sign_id = "retract_iid"; - $type = "relayable_retraction"; - } elseif ($item['verb'] === ACTIVITY_LIKE) { - $sql_sign_id = "iid"; - $type = "like"; - } else { - $sql_sign_id = "iid"; - $type = "comment"; - } - - logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); - - // fetch the original signature - - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", - intval($item["id"])); - - if (!$r) - return self::send_followup($item, $owner, $contact, $public_batch); - - $signature = $r[0]; - - // Old way - is used by the internal Friendica functions - /// @todo Change all signatur storing functions to the new format - if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) - $message = self::message_from_signatur($item, $signature); - else {// New way - $msg = json_decode($signature['signed_text'], true); - - $message = array(); - foreach ($msg AS $field => $data) { - if (!$item["deleted"]) { - if ($field == "author") - $field = "diaspora_handle"; - if ($field == "parent_type") - $field = "target_type"; - } - - $message[$field] = $data; - } - } - - if ($item["deleted"]) { - $signed_text = $message["target_guid"].';'.$message["target_type"]; - $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - } else - $message["parent_author_signature"] = self::signature($owner, $message); - - logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); - - return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); - } - - public static function send_retraction($item, $owner, $contact, $public_batch = false) { - - $myaddr = self::my_handle($owner); - - // Check whether the retraction is for a top-level post or whether it's a relayable - if ($item["uri"] !== $item["parent-uri"]) { - $msg_type = "relayable_retraction"; - $target_type = (($item["verb"] === ACTIVITY_LIKE) ? "Like" : "Comment"); - } else { - $msg_type = "signed_retraction"; - $target_type = "StatusMessage"; - } - - $signed_text = $item["guid"].";".$target_type; - - $message = array("target_guid" => $item['guid'], - "target_type" => $target_type, - "sender_handle" => $myaddr, - "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); - - return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); - } - - public static function send_mail($item, $owner, $contact) { - - $myaddr = self::my_handle($owner); - - $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($item["convid"]), - intval($item["uid"]) - ); - - if (!$r) { - logger("conversation not found."); - return; - } - $cnv = $r[0]; - - $conv = array( - "guid" => $cnv["guid"], - "subject" => $cnv["subject"], - "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), - "diaspora_handle" => $cnv["creator"], - "participant_handles" => $cnv["recips"] - ); - - $body = bb2diaspora($item["body"]); - $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); - - $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; - $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - - $msg = array( - "guid" => $item["guid"], - "parent_guid" => $cnv["guid"], - "parent_author_signature" => $sig, - "author_signature" => $sig, - "text" => $body, - "created_at" => $created, - "diaspora_handle" => $myaddr, - "conversation_guid" => $cnv["guid"] - ); - - if ($item["reply"]) { - $message = $msg; - $type = "message"; - } else { - $message = array("guid" => $cnv["guid"], - "subject" => $cnv["subject"], - "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), - "message" => $msg, - "diaspora_handle" => $cnv["creator"], - "participant_handles" => $cnv["recips"]); - - $type = "conversation"; - } - - return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); - } - - public static function send_profile($uid) { - - if (!$uid) - return; - - $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' - AND `uid` = %d AND `rel` != %d", - dbesc(NETWORK_DIASPORA), - intval($uid), - intval(CONTACT_IS_SHARING) - ); - if (!$recips) - return; - - $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr` - FROM `profile` - INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` - INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid` - WHERE `user`.`uid` = %d AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", - intval($uid) - ); - - if (!$r) - return; - - $profile = $r[0]; - - $handle = $profile["addr"]; - $first = ((strpos($profile['name'],' ') - ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); - $last = (($first === $profile['name']) ? '' : trim(substr($profile['name'], strlen($first)))); - $large = App::get_baseurl().'/photo/custom/300/'.$profile['uid'].'.jpg'; - $medium = App::get_baseurl().'/photo/custom/100/'.$profile['uid'].'.jpg'; - $small = App::get_baseurl().'/photo/custom/50/' .$profile['uid'].'.jpg'; - $searchable = (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false'); - - if ($searchable === 'true') { - $dob = '1000-00-00'; - - if (($profile['dob']) && ($profile['dob'] != '0000-00-00')) - $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') .'-'. datetime_convert('UTC','UTC',$profile['dob'],'m-d'); - - $about = $profile['about']; - $about = strip_tags(bbcode($about)); - - $location = formatted_location($profile); - $tags = ''; - if ($profile['pub_keywords']) { - $kw = str_replace(',',' ',$profile['pub_keywords']); - $kw = str_replace(' ',' ',$kw); - $arr = explode(' ',$profile['pub_keywords']); - if (count($arr)) { - for($x = 0; $x < 5; $x ++) { - if (trim($arr[$x])) - $tags .= '#'. trim($arr[$x]) .' '; - } - } - } - $tags = trim($tags); - } - - $message = array("diaspora_handle" => $handle, - "first_name" => $first, - "last_name" => $last, - "image_url" => $large, - "image_url_medium" => $medium, - "image_url_small" => $small, - "birthday" => $dob, - "gender" => $profile['gender'], - "bio" => $about, - "location" => $location, - "searchable" => $searchable, - "tag_string" => $tags); - - foreach($recips as $recip) - self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); - } -} -?> From 4b5e7007a7148d85858f6851bdb8900dfb05e7cd Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 14 Mar 2016 23:54:01 +0100 Subject: [PATCH 200/273] Bugfix: XML copy had problems with "&" --- include/diaspora.php | 12 ++++++------ include/xml.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 75cedeccd1..4c3cb41726 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -291,7 +291,7 @@ class diaspora { return self::receive_account_deletion($importer, $fields); case "comment": - return self::receive_comment($importer, $sender, $fields); + return self::receive_comment($importer, $sender, $fields, $msg["message"]); case "conversation": return self::receive_conversation($importer, $msg, $fields); @@ -324,7 +324,7 @@ class diaspora { return self::receive_retraction($importer, $sender, $fields); case "status_message": - return self::receive_status_message($importer, $fields); + return self::receive_status_message($importer, $fields, $msg["message"]); default: logger("Unknown message type ".$type); @@ -841,7 +841,7 @@ class diaspora { return true; } - private function receive_comment($importer, $sender, $data) { + private function receive_comment($importer, $sender, $data, $xml) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); $text = unxmlify($data->text); @@ -890,7 +890,7 @@ class diaspora { $datarray["parent-uri"] = $parent_item["uri"]; $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; - $datarray["object"] = json_encode($data); + $datarray["object"] = $xml; $datarray["body"] = diaspora2bb($text); @@ -1769,7 +1769,7 @@ class diaspora { return true; } - private function receive_status_message($importer, $data) { + private function receive_status_message($importer, $data, $xml) { $raw_message = unxmlify($data->raw_message); $guid = notags(unxmlify($data->guid)); @@ -1831,7 +1831,7 @@ class diaspora { $datarray["verb"] = ACTIVITY_POST; $datarray["gravity"] = GRAVITY_PARENT; - $datarray["object"] = json_encode($data); + $datarray["object"] = $xml; $datarray["body"] = $body; diff --git a/include/xml.php b/include/xml.php index c2313648ce..c74c23c473 100644 --- a/include/xml.php +++ b/include/xml.php @@ -62,7 +62,7 @@ class xml { function copy(&$source, &$target, $elementname) { if (count($source->children()) == 0) - $target->addChild($elementname, $source); + $target->addChild($elementname, xmlify($source)); else { $child = $target->addChild($elementname); foreach ($source->children() AS $childfield => $childentry) From eed55664fc61a0e285dcf5c814b39496537c7039 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 15 Mar 2016 00:26:28 +0100 Subject: [PATCH 201/273] Reshares now store the original XML as well. --- include/diaspora.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 4c3cb41726..3c3f5cb25a 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -318,7 +318,7 @@ class diaspora { return self::receive_request($importer, $fields); case "reshare": - return self::receive_reshare($importer, $fields); + return self::receive_reshare($importer, $fields, $msg["message"]); case "retraction": return self::receive_retraction($importer, $sender, $fields); @@ -1611,7 +1611,7 @@ class diaspora { return false; } - private function receive_reshare($importer, $data) { + private function receive_reshare($importer, $data, $xml) { $root_author = notags(unxmlify($data->root_author)); $root_guid = notags(unxmlify($data->root_guid)); $guid = notags(unxmlify($data->guid)); @@ -1652,7 +1652,7 @@ class diaspora { $datarray["verb"] = ACTIVITY_POST; $datarray["gravity"] = GRAVITY_PARENT; - $datarray["object"] = json_encode($data); + $datarray["object"] = $xml; $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], $original_item["guid"], $original_item["created"], $orig_url); From 3f2a23d48c0b588f0a31bb7cdb8b6490f307029f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 15 Mar 2016 07:05:10 +0100 Subject: [PATCH 202/273] Unused files are removed --- view/templates/diasp_dec_hdr.tpl | 9 ------ view/templates/diaspora_comment.tpl | 12 -------- view/templates/diaspora_comment_relay.tpl | 13 -------- view/templates/diaspora_conversation.tpl | 30 ------------------- view/templates/diaspora_like.tpl | 13 -------- view/templates/diaspora_like_relay.tpl | 14 --------- view/templates/diaspora_message.tpl | 17 ----------- view/templates/diaspora_photo.tpl | 14 --------- view/templates/diaspora_post.tpl | 13 -------- view/templates/diaspora_profile.tpl | 17 ----------- view/templates/diaspora_relay_retraction.tpl | 10 ------- .../diaspora_relayable_retraction.tpl | 12 -------- view/templates/diaspora_reshare.tpl | 14 --------- view/templates/diaspora_retract.tpl | 10 ------- view/templates/diaspora_share.tpl | 9 ------ view/templates/diaspora_signed_retract.tpl | 11 ------- 16 files changed, 218 deletions(-) delete mode 100644 view/templates/diasp_dec_hdr.tpl delete mode 100644 view/templates/diaspora_comment.tpl delete mode 100644 view/templates/diaspora_comment_relay.tpl delete mode 100644 view/templates/diaspora_conversation.tpl delete mode 100644 view/templates/diaspora_like.tpl delete mode 100644 view/templates/diaspora_like_relay.tpl delete mode 100644 view/templates/diaspora_message.tpl delete mode 100644 view/templates/diaspora_photo.tpl delete mode 100644 view/templates/diaspora_post.tpl delete mode 100644 view/templates/diaspora_profile.tpl delete mode 100644 view/templates/diaspora_relay_retraction.tpl delete mode 100644 view/templates/diaspora_relayable_retraction.tpl delete mode 100644 view/templates/diaspora_reshare.tpl delete mode 100644 view/templates/diaspora_retract.tpl delete mode 100644 view/templates/diaspora_share.tpl delete mode 100644 view/templates/diaspora_signed_retract.tpl diff --git a/view/templates/diasp_dec_hdr.tpl b/view/templates/diasp_dec_hdr.tpl deleted file mode 100644 index 136d1ca302..0000000000 --- a/view/templates/diasp_dec_hdr.tpl +++ /dev/null @@ -1,9 +0,0 @@ - -<decrypted_hdeader> - <iv>{{$inner_iv}}</iv> - <aes_key>{{$inner_key}}</aes_key> - <author> - <name>{{$author_name}}</name> - <uri>{{$author_uri}}</uri> - </author> -</decrypted_header> diff --git a/view/templates/diaspora_comment.tpl b/view/templates/diaspora_comment.tpl deleted file mode 100644 index 107cc73022..0000000000 --- a/view/templates/diaspora_comment.tpl +++ /dev/null @@ -1,12 +0,0 @@ - -<XML> - <post> - <comment> - <guid>{{$guid}}</guid> - <parent_guid>{{$parent_guid}}</parent_guid> - <author_signature>{{$authorsig}}</author_signature> - <text>{{$body}}</text> - <diaspora_handle>{{$handle}}</diaspora_handle> - </comment> - </post> -</XML> \ No newline at end of file diff --git a/view/templates/diaspora_comment_relay.tpl b/view/templates/diaspora_comment_relay.tpl deleted file mode 100644 index b4f84dc84e..0000000000 --- a/view/templates/diaspora_comment_relay.tpl +++ /dev/null @@ -1,13 +0,0 @@ - -<XML> - <post> - <comment> - <guid>{{$guid}}</guid> - <parent_guid>{{$parent_guid}}</parent_guid> - <parent_author_signature>{{$parentsig}}</parent_author_signature> - <author_signature>{{$authorsig}}</author_signature> - <text>{{$body}}</text> - <diaspora_handle>{{$handle}}</diaspora_handle> - </comment> - </post> -</XML> \ No newline at end of file diff --git a/view/templates/diaspora_conversation.tpl b/view/templates/diaspora_conversation.tpl deleted file mode 100644 index 28e4cdb98f..0000000000 --- a/view/templates/diaspora_conversation.tpl +++ /dev/null @@ -1,30 +0,0 @@ - -<XML> - <post> - <conversation> - <guid>{{$conv.guid}}</guid> - <subject>{{$conv.subject}}</subject> - <created_at>{{$conv.created_at}}</created_at> - - {{foreach $conv.messages as $msg}} - - <message> - <guid>{{$msg.guid}}</guid> - <parent_guid>{{$msg.parent_guid}}</parent_guid> - {{if $msg.parent_author_signature}} - <parent_author_signature>{{$msg.parent_author_signature}}</parent_author_signature> - {{/if}} - <author_signature>{{$msg.author_signature}}</author_signature> - <text>{{$msg.text}}</text> - <created_at>{{$msg.created_at}}</created_at> - <diaspora_handle>{{$msg.diaspora_handle}}</diaspora_handle> - <conversation_guid>{{$msg.conversation_guid}}</conversation_guid> - </message> - - {{/foreach}} - - <diaspora_handle>{{$conv.diaspora_handle}}</diaspora_handle> - <participant_handles>{{$conv.participant_handles}}</participant_handles> - </conversation> - </post> -</XML> diff --git a/view/templates/diaspora_like.tpl b/view/templates/diaspora_like.tpl deleted file mode 100644 index 165b0f5f7b..0000000000 --- a/view/templates/diaspora_like.tpl +++ /dev/null @@ -1,13 +0,0 @@ - -<XML> - <post> - <like> - <positive>{{$positive}}</positive> - <guid>{{$guid}}</guid> - <target_type>{{$target_type}}</target_type> - <parent_guid>{{$parent_guid}}</parent_guid> - <author_signature>{{$authorsig}}</author_signature> - <diaspora_handle>{{$handle}}</diaspora_handle> - </like> - </post> -</XML> diff --git a/view/templates/diaspora_like_relay.tpl b/view/templates/diaspora_like_relay.tpl deleted file mode 100644 index e1696e722a..0000000000 --- a/view/templates/diaspora_like_relay.tpl +++ /dev/null @@ -1,14 +0,0 @@ - -<XML> - <post> - <like> - <positive>{{$positive}}</positive> - <guid>{{$guid}}</guid> - <target_type>{{$target_type}}</target_type> - <parent_guid>{{$parent_guid}}</parent_guid> - <parent_author_signature>{{$parentsig}}</parent_author_signature> - <author_signature>{{$authorsig}}</author_signature> - <diaspora_handle>{{$handle}}</diaspora_handle> - </like> - </post> -</XML> diff --git a/view/templates/diaspora_message.tpl b/view/templates/diaspora_message.tpl deleted file mode 100644 index f9adb833b6..0000000000 --- a/view/templates/diaspora_message.tpl +++ /dev/null @@ -1,17 +0,0 @@ - -<XML> - <post> - <message> - <guid>{{$msg.guid}}</guid> - <parent_guid>{{$msg.parent_guid}}</parent_guid> - {{if $msg.parent_author_signature}} - <parent_author_signature>{{$msg.parent_author_signature}}</parent_author_signature> - {{/if}} - <author_signature>{{$msg.author_signature}}</author_signature> - <text>{{$msg.text}}</text> - <created_at>{{$msg.created_at}}</created_at> - <diaspora_handle>{{$msg.diaspora_handle}}</diaspora_handle> - <conversation_guid>{{$msg.conversation_guid}}</conversation_guid> - </message> - </post> -</XML> diff --git a/view/templates/diaspora_photo.tpl b/view/templates/diaspora_photo.tpl deleted file mode 100644 index 0330499577..0000000000 --- a/view/templates/diaspora_photo.tpl +++ /dev/null @@ -1,14 +0,0 @@ - -<XML> - <post> - <photo> - <guid>{{$guid}}</guid> - <diaspora_handle>{{$handle}}</diaspora_handle> - <public>{{$public}}</public> - <created_at>{{$created_at}}</created_at> - <remote_photo_path>{{$path}}</remote_photo_path> - <remote_photo_name>{{$filename}}</remote_photo_name> - <status_message_guid>{{$msg_guid}}</status_message_guid> - </photo> - </post> -</XML> diff --git a/view/templates/diaspora_post.tpl b/view/templates/diaspora_post.tpl deleted file mode 100644 index d6ba97327c..0000000000 --- a/view/templates/diaspora_post.tpl +++ /dev/null @@ -1,13 +0,0 @@ - -<XML> - <post> - <status_message> - <raw_message>{{$body}}</raw_message> - <guid>{{$guid}}</guid> - <diaspora_handle>{{$handle}}</diaspora_handle> - <public>{{$public}}</public> - <created_at>{{$created}}</created_at> - <provider_display_name>{{$provider}}</provider_display_name> - </status_message> - </post> -</XML> diff --git a/view/templates/diaspora_profile.tpl b/view/templates/diaspora_profile.tpl deleted file mode 100644 index afbbb1e1a2..0000000000 --- a/view/templates/diaspora_profile.tpl +++ /dev/null @@ -1,17 +0,0 @@ - -<XML> - <post><profile> - <diaspora_handle>{{$handle}}</diaspora_handle> - <first_name>{{$first}}</first_name> - <last_name>{{$last}}</last_name> - <image_url>{{$large}}</image_url> - <image_url_medium>{{$medium}}</image_url_medium> - <image_url_small>{{$small}}</image_url_small> - <birthday>{{$dob}}</birthday> - <gender>{{$gender}}</gender> - <bio>{{$about}}</bio> - <location>{{$location}}</location> - <searchable>{{$searchable}}</searchable> - <tag_string>{{$tags}}</tag_string> - </profile></post> -</XML> diff --git a/view/templates/diaspora_relay_retraction.tpl b/view/templates/diaspora_relay_retraction.tpl deleted file mode 100644 index c4b44cd05f..0000000000 --- a/view/templates/diaspora_relay_retraction.tpl +++ /dev/null @@ -1,10 +0,0 @@ -<XML> - <post> - <relayable_retraction> - <target_guid>{{$guid}}</target_guid> - <target_type>{{$type}}</target_type> - <sender_handle>{{$handle}}</sender_handle> - <target_author_signature>{{$signature}}</target_author_signature> - </relayable_retraction> - </post> -</XML> diff --git a/view/templates/diaspora_relayable_retraction.tpl b/view/templates/diaspora_relayable_retraction.tpl deleted file mode 100644 index 2ae776d1d0..0000000000 --- a/view/templates/diaspora_relayable_retraction.tpl +++ /dev/null @@ -1,12 +0,0 @@ - -<XML> - <post> - <relayable_retraction> - <parent_author_signature>{{$parentsig}}</parent_author_signature> - <target_guid>{{$guid}}</target_guid> - <target_type>{{$target_type}}</target_type> - <sender_handle>{{$handle}}</sender_handle> - <target_author_signature>{{$authorsig}}</target_author_signature> - </relayable_retraction> - </post> -</XML> diff --git a/view/templates/diaspora_reshare.tpl b/view/templates/diaspora_reshare.tpl deleted file mode 100644 index 6a4776b1bc..0000000000 --- a/view/templates/diaspora_reshare.tpl +++ /dev/null @@ -1,14 +0,0 @@ - -<XML> - <post> - <reshare> - <root_diaspora_id>{{$root_handle}}</root_diaspora_id> - <root_guid>{{$root_guid}}</root_guid> - <guid>{{$guid}}</guid> - <diaspora_handle>{{$handle}}</diaspora_handle> - <public>{{$public}}</public> - <created_at>{{$created}}</created_at> - <provider_display_name>{{$provider}}</provider_display_name> - </reshare> - </post> -</XML> diff --git a/view/templates/diaspora_retract.tpl b/view/templates/diaspora_retract.tpl deleted file mode 100644 index 3ddfcabfa7..0000000000 --- a/view/templates/diaspora_retract.tpl +++ /dev/null @@ -1,10 +0,0 @@ - -<XML> - <post> - <retraction> - <post_guid>{{$guid}}</post_guid> - <diaspora_handle>{{$handle}}</diaspora_handle> - <type>{{$type}}</type> - </retraction> - </post> -</XML> diff --git a/view/templates/diaspora_share.tpl b/view/templates/diaspora_share.tpl deleted file mode 100644 index 0142ab36c5..0000000000 --- a/view/templates/diaspora_share.tpl +++ /dev/null @@ -1,9 +0,0 @@ - -<XML> - <post> - <request> - <sender_handle>{{$sender}}</sender_handle> - <recipient_handle>{{$recipient}}</recipient_handle> - </request> - </post> -</XML> \ No newline at end of file diff --git a/view/templates/diaspora_signed_retract.tpl b/view/templates/diaspora_signed_retract.tpl deleted file mode 100644 index 83d0def4d2..0000000000 --- a/view/templates/diaspora_signed_retract.tpl +++ /dev/null @@ -1,11 +0,0 @@ - -<XML> - <post> - <signed_retraction> - <target_guid>{{$guid}}</target_guid> - <target_type>{{$type}}</target_type> - <sender_handle>{{$handle}}</sender_handle> - <target_author_signature>{{$signature}}</target_author_signature> - </signed_retraction> - </post> -</XML> From 8027854886a7d8a51fb3b97864e01f877403fc4a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 15 Mar 2016 07:18:11 +0100 Subject: [PATCH 203/273] Removed moved function --- include/diaspora.php | 1 - include/network.php | 58 -------------------------------------------- 2 files changed, 59 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 3c3f5cb25a..d5f2a21d9e 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -517,7 +517,6 @@ class diaspora { * @return string The id of the fcontact entry */ private function add_fcontact($arr, $update = false) { - /// @todo Remove this function from include/network.php if($update) { $r = q("UPDATE `fcontact` SET diff --git a/include/network.php b/include/network.php index c6379e407b..27459112d6 100644 --- a/include/network.php +++ b/include/network.php @@ -862,64 +862,6 @@ function parse_xml_string($s,$strict = true) { return $x; }} -function add_fcontact($arr,$update = false) { - - if($update) { - $r = q("UPDATE `fcontact` SET - `name` = '%s', - `photo` = '%s', - `request` = '%s', - `nick` = '%s', - `addr` = '%s', - `batch` = '%s', - `notify` = '%s', - `poll` = '%s', - `confirm` = '%s', - `alias` = '%s', - `pubkey` = '%s', - `updated` = '%s' - WHERE `url` = '%s' AND `network` = '%s'", - dbesc($arr['name']), - dbesc($arr['photo']), - dbesc($arr['request']), - dbesc($arr['nick']), - dbesc($arr['addr']), - dbesc($arr['batch']), - dbesc($arr['notify']), - dbesc($arr['poll']), - dbesc($arr['confirm']), - dbesc($arr['alias']), - dbesc($arr['pubkey']), - dbesc(datetime_convert()), - dbesc($arr['url']), - dbesc($arr['network']) - ); - } - else { - $r = q("insert into fcontact ( `url`,`name`,`photo`,`request`,`nick`,`addr`, - `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated` ) - values('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", - dbesc($arr['url']), - dbesc($arr['name']), - dbesc($arr['photo']), - dbesc($arr['request']), - dbesc($arr['nick']), - dbesc($arr['addr']), - dbesc($arr['batch']), - dbesc($arr['notify']), - dbesc($arr['poll']), - dbesc($arr['confirm']), - dbesc($arr['network']), - dbesc($arr['alias']), - dbesc($arr['pubkey']), - dbesc(datetime_convert()) - ); - } - - return $r; -} - - function scale_external_images($srctext, $include_link = true, $scale_replace = false) { // Suppress "view full size" From be001d171b385af3650cb8152542f3a60c645f63 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 15 Mar 2016 20:14:08 +0100 Subject: [PATCH 204/273] Values are sanitized, messages are not relayed when there is no signature --- include/diaspora.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index d5f2a21d9e..f4e3132959 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -221,7 +221,9 @@ class diaspora { logger('Message verified.'); - return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); + return array('message' => (string)$inner_decrypted, + 'author' => unxmlify($author_link), + 'key' => (string)$key); } @@ -1801,7 +1803,8 @@ class diaspora { if ($data->photo) { foreach ($data->photo AS $photo) - $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; + $body = "[img]".unxmlify($photo->remote_photo_path). + unxmlify($photo->remote_photo_name)."[/img]\n".$body; $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; } else { @@ -2355,8 +2358,10 @@ class diaspora { $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", intval($item["id"])); - if (!$r) - return self::send_followup($item, $owner, $contact, $public_batch); + if (!$r) { + logger("Couldn't fetch signatur for contact ".$contact["addr"]." at item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + return false; + } $signature = $r[0]; From 071ffd43bf66ff6478c09a4afc6ef010e34d8384 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 16 Mar 2016 11:44:45 +0100 Subject: [PATCH 205/273] DFRN: Mentions were imported as hash tags --- include/dfrn.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index ad04a91295..1c5ac2b012 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -2022,14 +2022,28 @@ class dfrn { $categories = $xpath->query("atom:category", $entry); if ($categories) { foreach ($categories AS $category) { - foreach($category->attributes AS $attributes) - if ($attributes->name == "term") { + $term = ""; + $scheme = ""; + foreach($category->attributes AS $attributes) { + if ($attributes->name == "term") $term = $attributes->textContent; + + if ($attributes->name == "scheme") + $scheme = $attributes->textContent; + } + + if (($term != "") AND ($scheme != "")) { + $parts = explode(":", $scheme); + if ((count($parts) >= 4) AND (array_shift($parts) == "X-DFRN")) { + $termhash = array_shift($parts); + $termurl = implode(":", $parts); + if(strlen($item["tag"])) $item["tag"] .= ","; - $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + $item["tag"] .= $termhash."[url=".$termurl."]".$term."[/url]"; } + } } } From 84a475e5897d5753c45b6cbb0a982893ee2cda1e Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 16 Mar 2016 16:49:54 +0100 Subject: [PATCH 206/273] Missing include --- include/diaspora.php | 1 + 1 file changed, 1 insertion(+) diff --git a/include/diaspora.php b/include/diaspora.php index f4e3132959..da69cdc799 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -13,6 +13,7 @@ require_once("include/socgraph.php"); require_once("include/group.php"); require_once("include/xml.php"); require_once("include/datetime.php"); +require_once("include/queue_fn.php"); /** * @brief This class contain functions to create and send Diaspora XML files From 468734a26ee6f7aeadcaaa5a2ba10c3b613c8dcc Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 16 Mar 2016 19:30:46 +0100 Subject: [PATCH 207/273] Added checklist --- include/diaspora.php | 51 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index da69cdc799..f7c38c2271 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -2,6 +2,36 @@ /** * @file include/diaspora.php * @brief The implementation of the diaspora protocol + * + * Checklist: + * + * Checked: + * - send status + * - send comment + * - send like + * - send mail + * - receive status + * - receive reshare + * - receive comment + * - receive like + * - receive connect request + * - receive profile data + * - receive mail + * - relay comment + * - relay like + * - + * - + * + * Unchecked: + * - receive account deletion + * - send share + * - send unshare + * - send status retraction + * - send comment retraction + * - send like retraction + * - relay comment retraction + * - relay like retraction + * - */ require_once("include/items.php"); @@ -2374,16 +2404,19 @@ class diaspora { $msg = json_decode($signature['signed_text'], true); $message = array(); - foreach ($msg AS $field => $data) { - if (!$item["deleted"]) { - if ($field == "author") - $field = "diaspora_handle"; - if ($field == "parent_type") - $field = "target_type"; - } + if (is_array($msg)) { + foreach ($msg AS $field => $data) { + if (!$item["deleted"]) { + if ($field == "author") + $field = "diaspora_handle"; + if ($field == "parent_type") + $field = "target_type"; + } - $message[$field] = $data; - } + $message[$field] = $data; + } + } else + logger("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$signature['signed_text'], LOGGER_DEBUG); } if ($item["deleted"]) { From e058feed289269e9edccd3047318d8b06dce7739 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 16 Mar 2016 21:27:07 +0100 Subject: [PATCH 208/273] Better reshare detection --- include/diaspora.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index f7c38c2271..4a6cbad131 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1600,7 +1600,7 @@ class diaspora { // Maybe it is already a reshared item? // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - if (self::is_reshare($r[0]["body"])) + if (self::is_reshare($r[0]["body"], false)) $r = array(); else return $r[0]; @@ -2130,7 +2130,7 @@ class diaspora { return self::build_and_transmit($owner, $contact, "retraction", $message); } - public static function is_reshare($body) { + public static function is_reshare($body, $complete = true) { $body = trim($body); // Skip if it isn't a pure repeated messages @@ -2147,6 +2147,10 @@ class diaspora { if ($body == $attributes) return(false); + // If we don't do the complete check we quit here + if (!$complete) + return true; + $guid = ""; preg_match("/guid='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") From 88fea17cab1fa3e1e8fb7bb0e2c3ea3480dbb8ee Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 17 Mar 2016 00:37:44 +0100 Subject: [PATCH 209/273] Everything tested, one open to-do --- include/delivery.php | 3 ++- include/diaspora.php | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/include/delivery.php b/include/delivery.php index 9ac9f2391b..fe33774382 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -243,7 +243,8 @@ function delivery_run(&$argv, &$argc){ if ((strlen($parent['allow_cid'])) || (strlen($parent['allow_gid'])) || (strlen($parent['deny_cid'])) - || (strlen($parent['deny_gid']))) { + || (strlen($parent['deny_gid'])) + || $parent["private"]) { $public_message = false; // private recipients, not public } diff --git a/include/diaspora.php b/include/diaspora.php index 4a6cbad131..870466497d 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -10,6 +10,11 @@ * - send comment * - send like * - send mail + * - send status retraction + * - send comment retraction on own post + * - send comment retraction on diaspora post + * - send like retraction on own post + * - send like retraction on diaspora post * - receive status * - receive reshare * - receive comment @@ -17,21 +22,21 @@ * - receive connect request * - receive profile data * - receive mail + * - receive comment retraction + * - receive like retraction * - relay comment * - relay like - * - - * - + * - relay comment retraction from diaspora + * - relay comment retraction from friendica + * - relay like retraction from diaspora + * - relay like retraction from friendica * - * Unchecked: + * Should work: * - receive account deletion * - send share * - send unshare - * - send status retraction - * - send comment retraction - * - send like retraction - * - relay comment retraction - * - relay like retraction - * - + * + * Unchecked: */ require_once("include/items.php"); @@ -2394,7 +2399,10 @@ class diaspora { intval($item["id"])); if (!$r) { - logger("Couldn't fetch signatur for contact ".$contact["addr"]." at item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + if ($item["deleted"]) + return self::send_retraction($item, $owner, $contact, $public_batch); + + logger("Couldn't fetch signatur for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); return false; } @@ -2436,6 +2444,9 @@ class diaspora { public static function send_retraction($item, $owner, $contact, $public_batch = false) { + /// @todo Fetch handle from every contact (via gcontact) + $itemaddr = self::handle_from_contact($item["contact-id"]); + $myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable @@ -2451,9 +2462,16 @@ class diaspora { $message = array("target_guid" => $item['guid'], "target_type" => $target_type, - "sender_handle" => $myaddr, + "sender_handle" => $itemaddr, "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); + if ($itemaddr != $myaddr) { + $message["parent_author_signature"] = $message["target_author_signature"]; + unset($message["target_author_signature"]); + } + + logger("Got message ".print_r($message, true), LOGGER_DEBUG); + return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); } From 16b92af71f0d838f7859869461f1acea25329d95 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 17 Mar 2016 12:24:23 +0100 Subject: [PATCH 210/273] Retraction do work as well --- database.sql | 4 +-- doc/database/db_sign.md | 1 - include/dbstructure.php | 2 -- include/diaspora.php | 73 ++++++++++++++++------------------------- include/items.php | 51 ---------------------------- include/like.php | 69 -------------------------------------- include/notifier.php | 7 ---- 7 files changed, 29 insertions(+), 178 deletions(-) diff --git a/database.sql b/database.sql index 02e5c9b378..89b821e23a 100644 --- a/database.sql +++ b/database.sql @@ -901,13 +901,11 @@ CREATE TABLE IF NOT EXISTS `session` ( CREATE TABLE IF NOT EXISTS `sign` ( `id` int(10) unsigned NOT NULL auto_increment, `iid` int(10) unsigned NOT NULL DEFAULT 0, - `retract_iid` int(10) unsigned NOT NULL DEFAULT 0, `signed_text` mediumtext NOT NULL, `signature` text NOT NULL, `signer` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY(`id`), - INDEX `iid` (`iid`), - INDEX `retract_iid` (`retract_iid`) + INDEX `iid` (`iid`) ) DEFAULT CHARSET=utf8; -- diff --git a/doc/database/db_sign.md b/doc/database/db_sign.md index 8de59ac675..6986613e59 100644 --- a/doc/database/db_sign.md +++ b/doc/database/db_sign.md @@ -5,7 +5,6 @@ Table sign | ------------ | ------------- | ---------------- | ---- | --- | ------- | --------------- | | id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment | | iid | item.id | int(10) unsigned | NO | MUL | 0 | | -| retract_iid | | int(10) unsigned | NO | MUL | 0 | | | signed_text | | mediumtext | NO | | NULL | | | signature | | text | NO | | NULL | | | signer | | varchar(255) | NO | | | | diff --git a/include/dbstructure.php b/include/dbstructure.php index e5e748bb24..e34e409023 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -1235,7 +1235,6 @@ function db_definition() { "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"), - "retract_iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"), "signed_text" => array("type" => "mediumtext", "not null" => "1"), "signature" => array("type" => "text", "not null" => "1"), "signer" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), @@ -1243,7 +1242,6 @@ function db_definition() { "indexes" => array( "PRIMARY" => array("id"), "iid" => array("iid"), - "retract_iid" => array("retract_iid"), ) ); $database["spam"] = array( diff --git a/include/diaspora.php b/include/diaspora.php index 870466497d..c888959d71 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -12,8 +12,8 @@ * - send mail * - send status retraction * - send comment retraction on own post - * - send comment retraction on diaspora post * - send like retraction on own post + * - send comment retraction on diaspora post * - send like retraction on diaspora post * - receive status * - receive reshare @@ -30,10 +30,10 @@ * - relay comment retraction from friendica * - relay like retraction from diaspora * - relay like retraction from friendica + * - send share * * Should work: * - receive account deletion - * - send share * - send unshare * * Unchecked: @@ -610,15 +610,21 @@ class diaspora { return $r; } - public static function handle_from_contact($contact_id) { + public static function handle_from_contact($contact_id, $gcontact_id = 0) { $handle = False; - logger("contact id is ".$contact_id, LOGGER_DEBUG); + logger("contact id is ".$contact_id." - gcontact id is ".$gcontact_id, LOGGER_DEBUG); + + if ($gcontact_id != 0) { + $r = q("SELECT `addr` FROM `gcontact` WHERE `id` = %d AND `addr` != ''", + intval($gcontact_id)); + if ($r) + return $r[0]["addr"]; + } $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", - intval($contact_id) - ); - if($r) { + intval($contact_id)); + if ($r) { $contact = $r[0]; logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); @@ -1759,16 +1765,6 @@ class diaspora { // Now check if the retraction needs to be relayed by us if($p[0]["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", - intval($r[0]["id"]), - dbesc(json_encode($data)) - ); - $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); - logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); - // notify others proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); } @@ -2380,28 +2376,21 @@ class diaspora { public static function send_relay($item, $owner, $contact, $public_batch = false) { - if ($item["deleted"]) { - $sql_sign_id = "retract_iid"; - $type = "relayable_retraction"; - } elseif ($item['verb'] === ACTIVITY_LIKE) { - $sql_sign_id = "iid"; + if ($item["deleted"]) + return self::send_retraction($item, $owner, $contact, $public_batch, true); + elseif ($item['verb'] === ACTIVITY_LIKE) $type = "like"; - } else { - $sql_sign_id = "iid"; + else $type = "comment"; - } logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); // fetch the original signature - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `iid` = %d LIMIT 1", intval($item["id"])); if (!$r) { - if ($item["deleted"]) - return self::send_retraction($item, $owner, $contact, $public_batch); - logger("Couldn't fetch signatur for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); return false; } @@ -2431,23 +2420,17 @@ class diaspora { logger("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$signature['signed_text'], LOGGER_DEBUG); } - if ($item["deleted"]) { - $signed_text = $message["target_guid"].';'.$message["target_type"]; - $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - } else - $message["parent_author_signature"] = self::signature($owner, $message); + $message["parent_author_signature"] = self::signature($owner, $message); logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - public static function send_retraction($item, $owner, $contact, $public_batch = false) { + public static function send_retraction($item, $owner, $contact, $public_batch = false, $relay = false) { - /// @todo Fetch handle from every contact (via gcontact) - $itemaddr = self::handle_from_contact($item["contact-id"]); - - $myaddr = self::my_handle($owner); + $itemaddr = self::handle_from_contact($item["contact-id"], $item["gcontact-id"]); + //$myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable if ($item["uri"] !== $item["parent-uri"]) { @@ -2458,17 +2441,17 @@ class diaspora { $target_type = "StatusMessage"; } + if ($relay AND ($item["uri"] !== $item["parent-uri"])) + $signature = "parent_author_signature"; + else + $signature = "target_author_signature"; + $signed_text = $item["guid"].";".$target_type; $message = array("target_guid" => $item['guid'], "target_type" => $target_type, "sender_handle" => $itemaddr, - "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); - - if ($itemaddr != $myaddr) { - $message["parent_author_signature"] = $message["target_author_signature"]; - unset($message["target_author_signature"]); - } + $signature => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); logger("Got message ".print_r($message, true), LOGGER_DEBUG); diff --git a/include/items.php b/include/items.php index 8d6b5b471c..f8c3149d58 100644 --- a/include/items.php +++ b/include/items.php @@ -1980,9 +1980,6 @@ function drop_item($id,$interactive = true) { intval($r[0]['id']) ); } - - // Add a relayable_retraction signature for Diaspora. - store_diaspora_retract_sig($item, $a->user, $a->get_baseurl()); } $drop_id = intval($item['id']); @@ -2115,51 +2112,3 @@ function posted_date_widget($url,$uid,$wall) { )); return $o; } - -function store_diaspora_retract_sig($item, $user, $baseurl) { - // Note that we can't add a target_author_signature - // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting - // the comment, that means we're the home of the post, and Diaspora will only - // check the parent_author_signature of retractions that it doesn't have to relay further - // - // I don't think this function gets called for an "unlike," but I'll check anyway - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG); - return; - } - - logger('drop_item: storing diaspora retraction signature'); - - $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - - if(local_user() == $item['uid']) { - - $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3); - $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256')); - } - else { - $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1", - $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id'] - ); - if(count($r)) { - // The below handle only works for NETWORK_DFRN. I think that's ok, because this function - // only handles DFRN deletes - $handle_baseurl_start = strpos($r['url'],'://') + 3; - $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start; - $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length); - $authorsig = ''; - } - } - - if(isset($handle)) - q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($item['id']), - dbesc($signed_text), - dbesc($authorsig), - dbesc($handle) - ); - - return; -} diff --git a/include/like.php b/include/like.php index 646e0727be..2e5367e51e 100644 --- a/include/like.php +++ b/include/like.php @@ -151,9 +151,6 @@ function do_like($item_id, $verb) { intval($like_item['id']) ); - // Save the author information for the unlike in case we need to relay to Diaspora - store_diaspora_like_retract_sig($activity, $item, $like_item, $contact); - $like_item_id = $like_item['id']; proc_run('php',"include/notifier.php","like","$like_item_id"); @@ -251,72 +248,6 @@ EOT; return true; } - - -function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact) { - // Note that we can only create a signature for a user of the local server. We don't have - // a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it - // means we are the relay, and for relayable_retractions, Diaspora - // only checks the parent_author_signature if it doesn't have to relay further - // - // If $item['resource-id'] exists, it means the item is a photo. Diaspora doesn't support - // likes on photos, so don't bother. - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('mod_like: diaspora support disabled, not storing like retraction signature', LOGGER_DEBUG); - return; - } - - logger('mod_like: storing diaspora like retraction signature'); - - if(($activity === ACTIVITY_LIKE) && (! $item['resource-id'])) { - $signed_text = $like_item['guid'] . ';' . 'Like'; - - // Only works for NETWORK_DFRN - $contact_baseurl_start = strpos($contact['url'],'://') + 3; - $contact_baseurl_length = strpos($contact['url'],'/profile') - $contact_baseurl_start; - $contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length); - $diaspora_handle = $contact['nick'] . '@' . $contact_baseurl; - - // This code could never had worked (the return values form the queries were used in a wrong way. - // Additionally it is needlessly complicated. Either the contact is owner or not. And we have this data already. -/* - // Get contact's private key if he's a user of the local Friendica server - $r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1", - dbesc($contact['url']) - ); - - if( $r) { - $contact_uid = $r['uid']; - $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", - intval($contact_uid) - ); -*/ - // Is the contact the owner? Then fetch the private key - if ($contact['self'] AND ($contact['uid'] > 0)) { - $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", - intval($contact['uid']) - ); - - if($r) - $authorsig = base64_encode(rsa_sign($signed_text,$r[0]['prvkey'],'sha256')); - } - - if(! isset($authorsig)) - $authorsig = ''; - - q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($like_item['id']), - dbesc($signed_text), - dbesc($authorsig), - dbesc($diaspora_handle) - ); - } - - return; -} - function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) { // Note that we can only create a signature for a user of the local server. We don't have // a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it diff --git a/include/notifier.php b/include/notifier.php index e65da3adf2..a46744f070 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -628,13 +628,6 @@ function notifier_run(&$argv, &$argc){ proc_run('php','include/pubsubpublish.php'); } - // If the item was deleted, clean up the `sign` table - /* if($target_item['deleted']) { - $r = q("DELETE FROM sign where `retract_iid` = %d", - intval($target_item['id']) - ); - } */ - logger('notifier: calling hooks', LOGGER_DEBUG); if($normal_mode) From bc579ff799595d7c4271e3562b554d5cecaf5b5b Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Thu, 17 Mar 2016 17:41:06 +0100 Subject: [PATCH 211/273] added title element to emoji images in main repository --- include/text.php | 70 ++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/include/text.php b/include/text.php index 07524e851d..c868499cc6 100644 --- a/include/text.php +++ b/include/text.php @@ -1148,41 +1148,41 @@ function smilies($s, $sample = false) { ); $icons = array( - '<img class="smiley" src="' . z_root() . '/images/smiley-heart.gif" alt="<3" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="</3" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="<\\3" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-smile.gif" alt=":-)" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-wink.gif" alt=";-)" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-frown.gif" alt=":-(" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-P" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-p" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\"" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\"" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-x" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-X" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-laughing.gif" alt=":-D" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-|" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-O" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt=":-O" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-thumbsup.gif" alt="\\o/" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o.O" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O.o" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o_O" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O_o" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-cry.gif" alt=":\'(" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-foot-in-mouth.gif" alt=":-!" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-undecided.gif" alt=":-/" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-embarassed.gif" alt=":-[" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-cool.gif" alt="8-)" />', - '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":beer" />', - '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":homebrew" />', - '<img class="smiley" src="' . z_root() . '/images/coffee.gif" alt=":coffee" />', - '<img class="smiley" src="' . z_root() . '/images/smiley-facepalm.gif" alt=":facepalm" />', - '<img class="smiley" src="' . z_root() . '/images/like.gif" alt=":like" />', - '<img class="smiley" src="' . z_root() . '/images/dislike.gif" alt=":dislike" />', - '<a href="http://friendica.com">~friendica <img class="smiley" src="' . z_root() . '/images/friendica-16.png" alt="~friendica" /></a>', - '<a href="http://redmatrix.me/">red<img class="smiley" src="' . z_root() . '/images/rm-16.png" alt="red" />matrix</a>', - '<a href="http://redmatrix.me/">red<img class="smiley" src="' . z_root() . '/images/rm-16.png" alt="red" />matrix</a>' + '<img class="smiley" src="' . z_root() . '/images/smiley-heart.gif" alt="<3" title="<3" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="</3" title="</3" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-brokenheart.gif" alt="<\\3" title="<\\3" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-smile.gif" alt=":-)" title=":-)" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-wink.gif" alt=";-)" title=";-)" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-frown.gif" alt=":-(" title=":-(" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-P" title=":-P" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-tongue-out.gif" alt=":-p" title=":-P" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\" title=":-\" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-\" title=":-\" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-x" title=":-x" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-kiss.gif" alt=":-X" title=":-X" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-laughing.gif" alt=":-D" title=":-D" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-|" title="8-|" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt="8-O" title="8-O" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-surprised.gif" alt=":-O" title="8-O" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-thumbsup.gif" alt="\\o/" title="\\o/" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o.O" title="o.O" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O.o" title="O.o" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="o_O" title="o_O" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-Oo.gif" alt="O_o" title="O_o" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-cry.gif" alt=":\'(" title=":\'("/>', + '<img class="smiley" src="' . z_root() . '/images/smiley-foot-in-mouth.gif" alt=":-!" title=":-!" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-undecided.gif" alt=":-/" title=":-/" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-embarassed.gif" alt=":-[" title=":-[" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-cool.gif" alt="8-)" title="8-)" />', + '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":beer" title=":beer" />', + '<img class="smiley" src="' . z_root() . '/images/beer_mug.gif" alt=":homebrew" title=":homebrew" />', + '<img class="smiley" src="' . z_root() . '/images/coffee.gif" alt=":coffee" title=":coffee" />', + '<img class="smiley" src="' . z_root() . '/images/smiley-facepalm.gif" alt=":facepalm" title=":facepalm" />', + '<img class="smiley" src="' . z_root() . '/images/like.gif" alt=":like" title=":like" />', + '<img class="smiley" src="' . z_root() . '/images/dislike.gif" alt=":dislike" title=":dislike" />', + '<a href="http://friendica.com">~friendica <img class="smiley" src="' . z_root() . '/images/friendica-16.png" alt="~friendica" title="~friendica" /></a>', + '<a href="http://redmatrix.me/">red<img class="smiley" src="' . z_root() . '/images/rm-16.png" alt="red#" title="red#" />matrix</a>', + '<a href="http://redmatrix.me/">red<img class="smiley" src="' . z_root() . '/images/rm-16.png" alt="red#matrix" title="red#matrix" />matrix</a>' ); $params = array('texts' => $texts, 'icons' => $icons, 'string' => $s); From fab48926d07907568aea5fb9766dad8a132a97c7 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 17 Mar 2016 23:44:18 +0100 Subject: [PATCH 212/273] Added some documentation, script to generate a basic doxygen header --- include/diaspora.php | 479 ++++++++++++++++++++++++++++++++++++++++- util/createdoxygen.php | 82 +++++++ 2 files changed, 552 insertions(+), 9 deletions(-) create mode 100755 util/createdoxygen.php diff --git a/include/diaspora.php b/include/diaspora.php index c888959d71..dd0efa1d73 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -56,6 +56,13 @@ require_once("include/queue_fn.php"); */ class diaspora { + /** + * @brief Return a list of relay servers + * + * This is an experimental Diaspora feature. + * + * @return array of relay servers + */ public static function relay_list() { $serverdata = get_config("system", "relay_server"); @@ -100,6 +107,15 @@ class diaspora { return $relay; } + /** + * @brief repairs a signature that was double encoded + * + * @param string $signature The signature + * @param string $handle The handle of the signature owner + * @param integer $level This value is only set inside this function to avoid endless loops + * + * @return the repaired signature + */ function repair_signature($signature, $handle = "", $level = 1) { if ($signature == "") @@ -120,7 +136,7 @@ class diaspora { /** * @brief: Decodes incoming Diaspora message * - * @param array $importer from user table + * @param array $importer Array of the importer user * @param string $xml urldecoded Diaspora salmon * * @return array @@ -610,6 +626,14 @@ class diaspora { return $r; } + /** + * @brief get a handle (user@domain.tld) from a given contact id or gcontact id + * + * @param int $contact_id The id in the contact table + * @param int $gcontact_id The id in the gcontact table + * + * @return string the handle + */ public static function handle_from_contact($contact_id, $gcontact_id = 0) { $handle = False; @@ -642,6 +666,14 @@ class diaspora { return $handle; } + /** + * @brief Get a contact id for a given handle + * + * @param int $uid The user id + * @param string $handle The handle in the format user@domain.tld + * + * @return The contact id + */ private function contact_by_handle($uid, $handle) { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", intval($uid), @@ -664,6 +696,15 @@ class diaspora { return false; } + /** + * @brief Check if posting is allowed for this contact + * + * @param array $importer Array of the importer user + * @param array $contact The contact that is checked + * @param bool $is_comment Is the check for a comment? + * + * @return bool is the contact allowed to post? + */ private function post_allow($importer, $contact, $is_comment = false) { // perhaps we were already sharing with this person. Now they're sharing with us. @@ -694,6 +735,15 @@ class diaspora { return false; } + /** + * @brief Fetches the contact id for a handle and checks if posting is allowed + * + * @param array $importer Array of the importer user + * @param string $handle The checked handle in the format user@domain.tld + * @param bool $is_comment Is the check for a comment? + * + * @return bool is posting allowed? + */ private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { $contact = self::contact_by_handle($importer["uid"], $handle); if (!$contact) { @@ -708,6 +758,14 @@ class diaspora { return $contact; } + /** + * @brief Does the message already exists on the system? + * + * @param int $uid The user id + * @param string $guid The guid of the message + * + * @return bool "true" if the message already was stored into the system + */ private function message_exists($uid, $guid) { $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", intval($uid), @@ -722,6 +780,11 @@ class diaspora { return false; } + /** + * @brief Checks for links to posts in a message + * + * @param array $item The item array + */ private function fetch_guid($item) { preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", function ($match) use ($item){ @@ -729,11 +792,26 @@ class diaspora { },$item["body"]); } + /** + * @brief sub function of "fetch_guid" + * + * @param array $match array containing a link that has to be checked for a message link + * @param array $item The item array + */ private function fetch_guid_sub($match, $item) { if (!self::store_by_guid($match[1], $item["author-link"])) self::store_by_guid($match[1], $item["owner-link"]); } + /** + * @brief Fetches an item with a given guid from a given server + * + * @param string $guid the message guid + * @param string $server The server address + * @param int $uid The user id of the user + * + * @return int the message id of the stored message or false + */ private function store_by_guid($guid, $server, $uid = 0) { $serverparts = parse_url($server); $server = $serverparts["scheme"]."://".$serverparts["host"]; @@ -751,6 +829,15 @@ class diaspora { return self::dispatch_public($msg); } + /** + * @brief + * + * @param string $guid message guid + * @param $server + * @param $level + * + * @return + */ private function message($guid, $server, $level = 0) { if ($level > 5) @@ -794,6 +881,16 @@ class diaspora { return $msg; } + /** + * @brief + * + * @param int $uid The user id + * @param string $guid message guid + * @param $author + * @param array $contact The contact that is checked + * + * @return + */ private function parent_item($uid, $guid, $author, $contact) { $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, `author-name`, `author-link`, `author-avatar`, @@ -829,6 +926,15 @@ class diaspora { } } + /** + * @brief + * + * @param array $contact The contact that is checked + * @param $person + * @param int $uid The user id + * + * @return + */ private function author_contact_by_url($contact, $person, $uid) { $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", @@ -844,10 +950,25 @@ class diaspora { return (array("cid" => $cid, "network" => $network)); } + /** + * @brief Is the profile a hubzilla profile? + * + * @param string $url The profile link + * + * @return bool is it a hubzilla server? + */ public static function is_redmatrix($url) { return(strstr($url, "/channel/")); } + /** + * @brief Generate a post link with a given handle and message guid + * + * @param $addr + * @param string $guid message guid + * + * @return string the post link + */ private function plink($addr, $guid) { $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); @@ -870,6 +991,14 @@ class diaspora { return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return + */ private function receive_account_deletion($importer, $data) { $author = notags(unxmlify($data->author)); @@ -884,6 +1013,16 @@ class diaspora { return true; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param string $sender The sender of the message + * @param object $data The message object + * @param string $xml The original XML of the message + * + * @return int The message id of the generated comment or "false" if there was an error + */ private function receive_comment($importer, $sender, $data, $xml) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); @@ -961,6 +1100,18 @@ class diaspora { return $message_id; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param array $contact The contact that is checked + * @param object $data The message object + * @param $msg + * @param $mesg + * @param $conversation + * + * @return + */ private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); @@ -1077,6 +1228,15 @@ class diaspora { )); } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param $msg + * @param object $data The message object + * + * @return + */ private function receive_conversation($importer, $msg, $data) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); @@ -1134,6 +1294,15 @@ class diaspora { return true; } + /** + * @brief + * + * @param array $contact The contact that is checked + * @param $parent_item + * @param string $guid message guid + * + * @return + */ private function construct_like_body($contact, $parent_item, $guid) { $bodyverb = t('%1$s likes %2$s\'s %3$s'); @@ -1144,6 +1313,14 @@ class diaspora { return sprintf($bodyverb, $ulink, $alink, $plink); } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param $parent_item + * + * @return + */ private function construct_like_object($importer, $parent_item) { $objtype = ACTIVITY_OBJ_NOTE; $link = '<link rel="alternate" type="text/html" href="'.App::get_baseurl()."/display/".$importer["nickname"]."/".$parent_item["id"].'" />'; @@ -1159,6 +1336,15 @@ class diaspora { return xml::from_array($xmldata, $xml, true); } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param string $sender The sender of the message + * @param object $data The message object + * + * @return int The message id of the generated like or "false" if there was an error + */ private function receive_like($importer, $sender, $data) { $positive = notags(unxmlify($data->positive)); $guid = notags(unxmlify($data->guid)); @@ -1247,6 +1433,14 @@ class diaspora { return $message_id; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return + */ private function receive_message($importer, $data) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); @@ -1318,27 +1512,59 @@ class diaspora { return true; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return bool always true + */ private function receive_participation($importer, $data) { // I'm not sure if we can fully support this message type return true; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return + */ private function receive_photo($importer, $data) { // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well return true; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return + */ private function receive_poll_participation($importer, $data) { // We don't support polls by now return true; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return + */ private function receive_profile($importer, $data) { $author = notags(unxmlify($data->author)); $contact = self::contact_by_handle($importer["uid"], $author); if (!$contact) - return; + return false; $name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : ""); $image_url = unxmlify($data->image_url); @@ -1418,6 +1644,14 @@ class diaspora { return true; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param array $contact The contact that is checked + * + * @return + */ private function receive_request_make_friend($importer, $contact) { $a = get_app(); @@ -1485,6 +1719,14 @@ class diaspora { } } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * + * @return + */ private function receive_request($importer, $data) { $author = unxmlify($data->author); $recipient = unxmlify($data->recipient); @@ -1598,6 +1840,15 @@ class diaspora { return true; } + /** + * @brief + * + * @param string $guid message guid + * @param $orig_author + * @param $author + * + * @return + */ private function original_item($guid, $orig_author, $author) { // Do we already have this item? @@ -1654,6 +1905,15 @@ class diaspora { return false; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * @param string $xml The original XML of the message + * + * @return + */ private function receive_reshare($importer, $data, $xml) { $root_author = notags(unxmlify($data->root_author)); $root_guid = notags(unxmlify($data->root_guid)); @@ -1719,6 +1979,15 @@ class diaspora { return $message_id; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param array $contact The contact that is checked + * @param object $data The message object + * + * @return + */ private function item_retraction($importer, $contact, $data) { $target_type = notags(unxmlify($data->target_type)); $target_guid = notags(unxmlify($data->target_guid)); @@ -1770,6 +2039,15 @@ class diaspora { } } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param string $sender The sender of the message + * @param object $data The message object + * + * @return + */ private function receive_retraction($importer, $sender, $data) { $target_type = notags(unxmlify($data->target_type)); @@ -1802,6 +2080,15 @@ class diaspora { return true; } + /** + * @brief + * + * @param array $importer Array of the importer user + * @param object $data The message object + * @param string $xml The original XML of the message + * + * @return + */ private function receive_status_message($importer, $data, $xml) { $raw_message = unxmlify($data->raw_message); @@ -1895,6 +2182,13 @@ class diaspora { * Here are all the functions that are needed to transmit data with the Diaspora protocol * ******************************************************************************************/ + /** + * @brief + * + * @param $me + * + * @return + */ private function my_handle($me) { if ($contact["addr"] != "") return $contact["addr"]; @@ -1904,6 +2198,17 @@ class diaspora { return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); } + /** + * @brief + * + * @param $msg + * @param $user + * @param array $contact The contact that is checked + * @param $prvkey + * @param $pubkey + * + * @return + */ private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { logger("Message: ".$msg, LOGGER_DATA); @@ -1939,6 +2244,17 @@ class diaspora { return $magic_env; } + /** + * @brief + * + * @param $msg + * @param $user + * @param array $contact The contact that is checked + * @param $prvkey + * @param $pubkey + * + * @return + */ private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { logger("Message: ".$msg, LOGGER_DATA); @@ -2018,6 +2334,18 @@ class diaspora { return $magic_env; } + /** + * @brief + * + * @param $msg + * @param $user + * @param array $contact The contact that is checked + * @param $prvkey + * @param $pubkey + * @param $public + * + * @return + */ private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { if ($public) @@ -2030,6 +2358,14 @@ class diaspora { return $slap; } + /** + * @brief + * + * @param array $owner the array of the item owner + * @param $message + * + * @return + */ private function signature($owner, $message) { $sigmsg = $message; unset($sigmsg["author_signature"]); @@ -2040,6 +2376,18 @@ class diaspora { return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); } + /** + * @brief + * + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * @param $slap + * @param bool $public_batch Is it a public post? + * @param $queue_run + * @param string $guid message guid + * + * @return + */ public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { $a = get_app(); @@ -2092,6 +2440,19 @@ class diaspora { } + /** + * @brief + * + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * @param $type + * @param $message + * @param bool $public_batch Is it a public post? + * @param string $guid message guid + * @param $spool + * + * @return + */ private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { $data = array("XML" => array("post" => array($type => $message))); @@ -2114,6 +2475,14 @@ class diaspora { return $return_code; } + /** + * @brief + * + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * + * @return int The result of the transmission + */ public static function send_share($owner,$contact) { $message = array("sender_handle" => self::my_handle($owner), @@ -2122,6 +2491,14 @@ class diaspora { return self::build_and_transmit($owner, $contact, "request", $message); } + /** + * @brief + * + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * + * @return int The result of the transmission + */ public static function send_unshare($owner,$contact) { $message = array("post_guid" => $owner["guid"], @@ -2131,6 +2508,14 @@ class diaspora { return self::build_and_transmit($owner, $contact, "retraction", $message); } + /** + * @brief + * + * @param $body + * @param $complete + * + * @return + */ public static function is_reshare($body, $complete = true) { $body = trim($body); @@ -2202,6 +2587,16 @@ class diaspora { return($ret); } + /** + * @brief + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * @param bool $public_batch Is it a public post? + * + * @return int The result of the transmission + */ public static function send_status($item, $owner, $contact, $public_batch = false) { $myaddr = self::my_handle($owner); @@ -2269,10 +2664,16 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } + /** + * @brief + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * + * @return + */ private function construct_like($item, $owner) { - $myaddr = self::my_handle($owner); - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", dbesc($item["thr-parent"])); if(!$p) @@ -2288,13 +2689,19 @@ class diaspora { "target_type" => $target_type, "parent_guid" => $parent["guid"], "author_signature" => $authorsig, - "diaspora_handle" => $myaddr)); + "diaspora_handle" => self::my_handle($owner))); } + /** + * @brief + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * + * @return + */ private function construct_comment($item, $owner) { - $myaddr = self::my_handle($owner); - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", intval($item["parent"]), intval($item["parent"]) @@ -2311,9 +2718,19 @@ class diaspora { "parent_guid" => $parent["guid"], "author_signature" => "", "text" => $text, - "diaspora_handle" => $myaddr)); + "diaspora_handle" => self::my_handle($owner))); } + /** + * @brief Send a like or a comment + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * @param bool $public_batch Is it a public post? + * + * @return int The result of the transmission + */ public static function send_followup($item,$owner,$contact,$public_batch = false) { if($item['verb'] === ACTIVITY_LIKE) { @@ -2332,6 +2749,14 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } + /** + * @brief + * + * @param array $item The item that will be exported + * @param $signature + * + * @return int The result of the transmission + */ private function message_from_signatur($item, $signature) { // Split the signed text @@ -2374,6 +2799,16 @@ class diaspora { return $message; } + /** + * @brief + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * @param bool $public_batch Is it a public post? + * + * @return int The result of the transmission + */ public static function send_relay($item, $owner, $contact, $public_batch = false) { if ($item["deleted"]) @@ -2427,10 +2862,20 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } + /** + * @brief + * + * @param array $item The item that will be exported + * @param array $owner the array of the item owner + * @param array $contact The contact that is checked + * @param bool $public_batch Is it a public post? + * @param $relay + * + * @return int The result of the transmission + */ public static function send_retraction($item, $owner, $contact, $public_batch = false, $relay = false) { $itemaddr = self::handle_from_contact($item["contact-id"], $item["gcontact-id"]); - //$myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable if ($item["uri"] !== $item["parent-uri"]) { @@ -2458,6 +2903,15 @@ class diaspora { return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); } + /** + * @brief + * + * @param array $item The item that will be exported + * @param array $owner The owner + * @param array $contact The contact that is checked + * + * @return int The result of the transmission + */ public static function send_mail($item, $owner, $contact) { $myaddr = self::my_handle($owner); @@ -2515,6 +2969,13 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); } + /** + * @brief + * + * @param int $uid The user id + * + * @return int The result of the transmission + */ public static function send_profile($uid) { if (!$uid) diff --git a/util/createdoxygen.php b/util/createdoxygen.php new file mode 100755 index 0000000000..d48114b671 --- /dev/null +++ b/util/createdoxygen.php @@ -0,0 +1,82 @@ +#!/usr/bin/php +<?php +if (count($_SERVER["argv"]) < 2) + die("usage: createdoxygen.php file\n"); + +$file = $_SERVER["argv"][1]; +$data = file_get_contents($file); + +$lines = explode("\n", $data); + +$previous = ""; + +foreach ($lines AS $line) { + $line = rtrim(trim($line, "\r")); + + if (strstr(strtolower($line), "function")) { + $detect = strtolower(trim($line)); + $detect = implode(" ", explode(" ", $detect)); + + $found = false; + + if (substr($detect, 0, 9) == "function ") + $found = true; + + if (substr($detect, 0, 17) == "private function ") + $found = true; + + if (substr($detect, 0, 23) == "public static function ") + $found = true; + + if (substr($detect, 0, 10) == "function (") + $found = false; + + if ($found and (trim($previous) == "*/")) + $found = false; + + if ($found and !strstr($detect, "{")) + $found = false; + + if ($found) { + echo add_documentation($line); + } + } + echo $line."\n"; + $previous = $line; +} + +function add_documentation($line) { + + $trimmed = ltrim($line); + $length = strlen($line) - strlen($trimmed); + $space = substr($line, 0, $length); + + $block = $space."/**\n". + $space." * @brief \n". + $space." *\n"; /**/ + + + $left = strpos($line, "("); + $line = substr($line, $left + 1); + + $right = strpos($line, ")"); + $line = trim(substr($line, 0, $right)); + + if ($line != "") { + $parameters = explode(",", $line); + foreach ($parameters AS $parameter) { + $parameter = trim($parameter); + $splitted = explode("=", $parameter); + + $block .= $space." * @param ".trim($splitted[0], "& ")."\n"; + } + if (count($parameters) > 0) + $block .= $space." *\n"; + } + + $block .= $space." * @return \n". + $space." */\n"; + + return $block; +} +?> From e74587b37576df113e05a56bc7a4d29f736a4fb5 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 18 Mar 2016 08:07:23 +0100 Subject: [PATCH 213/273] Just some more documentation --- include/diaspora.php | 149 ++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 74 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index dd0efa1d73..14ff6e42f8 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -830,13 +830,13 @@ class diaspora { } /** - * @brief + * @brief Fetches a message from a server * * @param string $guid message guid - * @param $server - * @param $level + * @param string $server The url of the server + * @param int $level Endless loop prevention * - * @return + * @return array of message, author and public key */ private function message($guid, $server, $level = 0) { @@ -882,14 +882,14 @@ class diaspora { } /** - * @brief + * @brief Fetches the item record of a given guid * * @param int $uid The user id * @param string $guid message guid - * @param $author + * @param string $author The handle of the item * @param array $contact The contact that is checked * - * @return + * @return array the item record */ private function parent_item($uid, $guid, $author, $contact) { $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, @@ -927,13 +927,13 @@ class diaspora { } /** - * @brief + * @brief returns contact details * - * @param array $contact The contact that is checked - * @param $person + * @param array $contact The default contact if the person isn't found + * @param array $person The record of the person * @param int $uid The user id * - * @return + * @return array of contact id and network type */ private function author_contact_by_url($contact, $person, $uid) { @@ -964,7 +964,7 @@ class diaspora { /** * @brief Generate a post link with a given handle and message guid * - * @param $addr + * @param string $addr The user handle * @param string $guid message guid * * @return string the post link @@ -1101,16 +1101,16 @@ class diaspora { } /** - * @brief + * @brief processes and stores private messages * * @param array $importer Array of the importer user * @param array $contact The contact that is checked * @param object $data The message object - * @param $msg - * @param $mesg - * @param $conversation + * @param array $msg Array of the processed message, author handle and key + * @param object $mesg The private message + * @param array $conversation The conversation record to which this message belongs * - * @return + * @return bool "true" if it was successful */ private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { $guid = notags(unxmlify($data->guid)); @@ -1226,13 +1226,14 @@ class diaspora { "verb" => ACTIVITY_POST, "otype" => "mail" )); + return true; } /** * @brief * * @param array $importer Array of the importer user - * @param $msg + * @param array $msg Array of the processed message, author handle and key * @param object $data The message object * * @return @@ -2183,11 +2184,11 @@ class diaspora { ******************************************************************************************/ /** - * @brief + * @brief returnes the handle of a contact * - * @param $me + * @param array $me contact array * - * @return + * @return string the handle in the format user@domain.tld */ private function my_handle($me) { if ($contact["addr"] != "") @@ -2199,15 +2200,15 @@ class diaspora { } /** - * @brief + * @brief Creates the envelope for a public message * - * @param $msg - * @param $user - * @param array $contact The contact that is checked - * @param $prvkey - * @param $pubkey + * @param string $msg The message that is to be transmitted + * @param array $user The record of the sender + * @param array $contact Target of the communication + * @param string $prvkey The private key of the sender + * @param string $pubkey The public key of the receiver * - * @return + * @return string The envelope */ private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { @@ -2245,15 +2246,15 @@ class diaspora { } /** - * @brief + * @brief Creates the envelope for a private message * - * @param $msg - * @param $user - * @param array $contact The contact that is checked - * @param $prvkey - * @param $pubkey + * @param string $msg The message that is to be transmitted + * @param array $user The record of the sender + * @param array $contact Target of the communication + * @param string $prvkey The private key of the sender + * @param string $pubkey The public key of the receiver * - * @return + * @return string The envelope */ private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { @@ -2335,14 +2336,14 @@ class diaspora { } /** - * @brief + * @brief Create the envelope for a message * - * @param $msg - * @param $user - * @param array $contact The contact that is checked - * @param $prvkey - * @param $pubkey - * @param $public + * @param string $msg The message that is to be transmitted + * @param array $user The record of the sender + * @param array $contact Target of the communication + * @param string $prvkey The private key of the sender + * @param string $pubkey The public key of the receiver + * @param bool $public Is the message public? * * @return */ @@ -2359,12 +2360,12 @@ class diaspora { } /** - * @brief + * @brief Creates a signature for a message * - * @param array $owner the array of the item owner - * @param $message + * @param array $owner the array of the owner of the message + * @param array $message The message that is to be signed * - * @return + * @return string The signature */ private function signature($owner, $message) { $sigmsg = $message; @@ -2377,16 +2378,16 @@ class diaspora { } /** - * @brief + * @brief Transmit a message to a target server * * @param array $owner the array of the item owner - * @param array $contact The contact that is checked - * @param $slap + * @param array $contact Target of the communication + * @param string $slap The message that is to be transmitted * @param bool $public_batch Is it a public post? - * @param $queue_run + * @param bool $queue_run Is the transmission called from the queue? * @param string $guid message guid * - * @return + * @return int Result of the transmission */ public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { @@ -2444,14 +2445,14 @@ class diaspora { * @brief * * @param array $owner the array of the item owner - * @param array $contact The contact that is checked - * @param $type - * @param $message + * @param array $contact Target of the communication + * @param string $type The message type + * @param array $message The message data * @param bool $public_batch Is it a public post? * @param string $guid message guid - * @param $spool + * @param bool $spool Should the transmission be spooled or transmitted? * - * @return + * @return int Result of the transmission */ private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { @@ -2479,7 +2480,7 @@ class diaspora { * @brief * * @param array $owner the array of the item owner - * @param array $contact The contact that is checked + * @param array $contact Target of the communication * * @return int The result of the transmission */ @@ -2495,7 +2496,7 @@ class diaspora { * @brief * * @param array $owner the array of the item owner - * @param array $contact The contact that is checked + * @param array $contact Target of the communication * * @return int The result of the transmission */ @@ -2509,12 +2510,12 @@ class diaspora { } /** - * @brief + * @brief Checks a message body if it is a reshare * - * @param $body - * @param $complete + * @param string $body The message body that is to be check + * @param bool $complete Should it be a complete check or a simple check? * - * @return + * @return array|bool Reshare details or "false" if no reshare */ public static function is_reshare($body, $complete = true) { $body = trim($body); @@ -2592,7 +2593,7 @@ class diaspora { * * @param array $item The item that will be exported * @param array $owner the array of the item owner - * @param array $contact The contact that is checked + * @param array $contact Target of the communication * @param bool $public_batch Is it a public post? * * @return int The result of the transmission @@ -2726,7 +2727,7 @@ class diaspora { * * @param array $item The item that will be exported * @param array $owner the array of the item owner - * @param array $contact The contact that is checked + * @param array $contact Target of the communication * @param bool $public_batch Is it a public post? * * @return int The result of the transmission @@ -2750,14 +2751,14 @@ class diaspora { } /** - * @brief + * @brief Creates a message from a signature record entry * * @param array $item The item that will be exported - * @param $signature + * @param array $signature The entry of the "sign" record * - * @return int The result of the transmission + * @return string The message */ - private function message_from_signatur($item, $signature) { + private function message_from_signature($item, $signature) { // Split the signed text $signed_parts = explode(";", $signature['signed_text']); @@ -2804,7 +2805,7 @@ class diaspora { * * @param array $item The item that will be exported * @param array $owner the array of the item owner - * @param array $contact The contact that is checked + * @param array $contact Target of the communication * @param bool $public_batch Is it a public post? * * @return int The result of the transmission @@ -2835,7 +2836,7 @@ class diaspora { // Old way - is used by the internal Friendica functions /// @todo Change all signatur storing functions to the new format if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) - $message = self::message_from_signatur($item, $signature); + $message = self::message_from_signature($item, $signature); else {// New way $msg = json_decode($signature['signed_text'], true); @@ -2863,13 +2864,13 @@ class diaspora { } /** - * @brief + * @brief Sends a retraction (deletion) of a message, like or comment * * @param array $item The item that will be exported * @param array $owner the array of the item owner - * @param array $contact The contact that is checked + * @param array $contact Target of the communication * @param bool $public_batch Is it a public post? - * @param $relay + * @param bool $relay Is the retraction transmitted from a relay? * * @return int The result of the transmission */ @@ -2908,7 +2909,7 @@ class diaspora { * * @param array $item The item that will be exported * @param array $owner The owner - * @param array $contact The contact that is checked + * @param array $contact Target of the communication * * @return int The result of the transmission */ From a2507804f2eab92df5ff721b3c836da5d1ca7461 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 18 Mar 2016 16:42:10 +0100 Subject: [PATCH 214/273] There is no table "sess_data" - this database call is useless --- include/session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/session.php b/include/session.php index 11641d6cea..12551efc42 100644 --- a/include/session.php +++ b/include/session.php @@ -69,7 +69,7 @@ function ref_session_destroy ($id) { if(! function_exists('ref_session_gc')) { function ref_session_gc($expire) { q("DELETE FROM `session` WHERE `expire` < %d", dbesc(time())); - q("OPTIMIZE TABLE `sess_data`"); + //q("OPTIMIZE TABLE `sess_data`"); return true; }} From 5c44a787bf0f6c514ee33cb75faee544618eb775 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 18 Mar 2016 22:28:20 +0100 Subject: [PATCH 215/273] The documentation should now be complete --- include/diaspora.php | 118 +++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 60 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 14ff6e42f8..4e1b300507 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -110,6 +110,8 @@ class diaspora { /** * @brief repairs a signature that was double encoded * + * The function is unused at the moment. It was copied from the old implementation. + * * @param string $signature The signature * @param string $handle The handle of the signature owner * @param integer $level This value is only set inside this function to avoid endless loops @@ -887,7 +889,7 @@ class diaspora { * @param int $uid The user id * @param string $guid message guid * @param string $author The handle of the item - * @param array $contact The contact that is checked + * @param array $contact The contact of the item owner * * @return array the item record */ @@ -992,12 +994,12 @@ class diaspora { } /** - * @brief + * @brief Processes an account deletion * * @param array $importer Array of the importer user * @param object $data The message object * - * @return + * @return bool Success */ private function receive_account_deletion($importer, $data) { $author = notags(unxmlify($data->author)); @@ -1014,7 +1016,7 @@ class diaspora { } /** - * @brief + * @brief Processes an incoming comment * * @param array $importer Array of the importer user * @param string $sender The sender of the message @@ -1104,7 +1106,7 @@ class diaspora { * @brief processes and stores private messages * * @param array $importer Array of the importer user - * @param array $contact The contact that is checked + * @param array $contact The contact of the message * @param object $data The message object * @param array $msg Array of the processed message, author handle and key * @param object $mesg The private message @@ -1230,13 +1232,13 @@ class diaspora { } /** - * @brief + * @brief Processes new private messages (answers to private messages are processed elsewhere) * * @param array $importer Array of the importer user * @param array $msg Array of the processed message, author handle and key * @param object $data The message object * - * @return + * @return bool Success */ private function receive_conversation($importer, $msg, $data) { $guid = notags(unxmlify($data->guid)); @@ -1296,13 +1298,13 @@ class diaspora { } /** - * @brief + * @brief Creates the body for a "like" message * - * @param array $contact The contact that is checked - * @param $parent_item + * @param array $contact The contact that send us the "like" + * @param array $parent_item The item array of the parent item * @param string $guid message guid * - * @return + * @return string the body */ private function construct_like_body($contact, $parent_item, $guid) { $bodyverb = t('%1$s likes %2$s\'s %3$s'); @@ -1315,12 +1317,12 @@ class diaspora { } /** - * @brief + * @brief Creates a XML object for a "like" * * @param array $importer Array of the importer user - * @param $parent_item + * @param array $parent_item The item array of the parent item * - * @return + * @return string The XML */ private function construct_like_object($importer, $parent_item) { $objtype = ACTIVITY_OBJ_NOTE; @@ -1338,7 +1340,7 @@ class diaspora { } /** - * @brief + * @brief Processes "like" messages * * @param array $importer Array of the importer user * @param string $sender The sender of the message @@ -1435,12 +1437,12 @@ class diaspora { } /** - * @brief + * @brief Processes private messages * * @param array $importer Array of the importer user * @param object $data The message object * - * @return + * @return bool Success? */ private function receive_message($importer, $data) { $guid = notags(unxmlify($data->guid)); @@ -1514,7 +1516,7 @@ class diaspora { } /** - * @brief + * @brief Processes participations - unsupported by now * * @param array $importer Array of the importer user * @param object $data The message object @@ -1527,12 +1529,12 @@ class diaspora { } /** - * @brief + * @brief Processes photos - unneeded * * @param array $importer Array of the importer user * @param object $data The message object * - * @return + * @return bool always true */ private function receive_photo($importer, $data) { // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well @@ -1540,12 +1542,12 @@ class diaspora { } /** - * @brief + * @brief Processes poll participations - unssupported * * @param array $importer Array of the importer user * @param object $data The message object * - * @return + * @return bool always true */ private function receive_poll_participation($importer, $data) { // We don't support polls by now @@ -1553,12 +1555,12 @@ class diaspora { } /** - * @brief + * @brief Processes incoming profile updates * * @param array $importer Array of the importer user * @param object $data The message object * - * @return + * @return bool Success */ private function receive_profile($importer, $data) { $author = notags(unxmlify($data->author)); @@ -1646,12 +1648,10 @@ class diaspora { } /** - * @brief + * @brief Processes incoming friend requests * * @param array $importer Array of the importer user - * @param array $contact The contact that is checked - * - * @return + * @param array $contact The contact that send the request */ private function receive_request_make_friend($importer, $contact) { @@ -1714,26 +1714,24 @@ class diaspora { $i = item_store($arr); if($i) proc_run("php", "include/notifier.php", "activity", $i); - } - } } /** - * @brief + * @brief Processes incoming sharing notification * * @param array $importer Array of the importer user * @param object $data The message object * - * @return + * @return bool Success */ private function receive_request($importer, $data) { $author = unxmlify($data->author); $recipient = unxmlify($data->recipient); if (!$author || !$recipient) - return; + return false; $contact = self::contact_by_handle($importer["uid"],$author); @@ -1842,13 +1840,13 @@ class diaspora { } /** - * @brief + * @brief Fetches a message with a given guid * * @param string $guid message guid - * @param $orig_author - * @param $author + * @param string $orig_author handle of the original post + * @param string $author handle of the sharer * - * @return + * @return array The fetched item */ private function original_item($guid, $orig_author, $author) { @@ -1907,13 +1905,13 @@ class diaspora { } /** - * @brief + * @brief Processes a reshare message * * @param array $importer Array of the importer user * @param object $data The message object * @param string $xml The original XML of the message * - * @return + * @return int the message id */ private function receive_reshare($importer, $data, $xml) { $root_author = notags(unxmlify($data->root_author)); @@ -1981,13 +1979,13 @@ class diaspora { } /** - * @brief + * @brief Processes retractions * * @param array $importer Array of the importer user - * @param array $contact The contact that is checked + * @param array $contact The contact of the item owner * @param object $data The message object * - * @return + * @return bool success */ private function item_retraction($importer, $contact, $data) { $target_type = notags(unxmlify($data->target_type)); @@ -2038,16 +2036,18 @@ class diaspora { // notify others proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); } + + return true; } /** - * @brief + * @brief Receives retraction messages * * @param array $importer Array of the importer user * @param string $sender The sender of the message * @param object $data The message object * - * @return + * @return bool Success */ private function receive_retraction($importer, $sender, $data) { $target_type = notags(unxmlify($data->target_type)); @@ -2082,13 +2082,13 @@ class diaspora { } /** - * @brief + * @brief Receives status messages * * @param array $importer Array of the importer user * @param object $data The message object * @param string $xml The original XML of the message * - * @return + * @return int The message id of the newly created item */ private function receive_status_message($importer, $data, $xml) { @@ -2345,7 +2345,7 @@ class diaspora { * @param string $pubkey The public key of the receiver * @param bool $public Is the message public? * - * @return + * @return string The message that will be transmitted to other servers */ private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { @@ -2442,7 +2442,7 @@ class diaspora { /** - * @brief + * @brief Builds and transmit messages * * @param array $owner the array of the item owner * @param array $contact Target of the communication @@ -2477,7 +2477,7 @@ class diaspora { } /** - * @brief + * @brief Sends a "share" message * * @param array $owner the array of the item owner * @param array $contact Target of the communication @@ -2493,7 +2493,7 @@ class diaspora { } /** - * @brief + * @brief sends an "unshare" * * @param array $owner the array of the item owner * @param array $contact Target of the communication @@ -2589,7 +2589,7 @@ class diaspora { } /** - * @brief + * @brief Sends a post * * @param array $item The item that will be exported * @param array $owner the array of the item owner @@ -2666,12 +2666,12 @@ class diaspora { } /** - * @brief + * @brief Creates a "like" object * * @param array $item The item that will be exported * @param array $owner the array of the item owner * - * @return + * @return array The data for a "like" */ private function construct_like($item, $owner) { @@ -2694,12 +2694,12 @@ class diaspora { } /** - * @brief + * @brief Creates the object for a comment * * @param array $item The item that will be exported * @param array $owner the array of the item owner * - * @return + * @return array The data for a comment */ private function construct_comment($item, $owner) { @@ -2801,7 +2801,7 @@ class diaspora { } /** - * @brief + * @brief Relays messages (like, comment, retraction) to other servers if we are the thread owner * * @param array $item The item that will be exported * @param array $owner the array of the item owner @@ -2905,7 +2905,7 @@ class diaspora { } /** - * @brief + * @brief Sends a mail * * @param array $item The item that will be exported * @param array $owner The owner @@ -2971,11 +2971,9 @@ class diaspora { } /** - * @brief + * @brief Sends profile data * * @param int $uid The user id - * - * @return int The result of the transmission */ public static function send_profile($uid) { From d676ae0f32a33e0f75c28d31f32f3d9476b4efd6 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 19 Mar 2016 15:49:47 +0100 Subject: [PATCH 216/273] The signature creation now moved into the Diaspora class. That's much cleaner. --- include/diaspora.php | 124 +++++++++++++++++++++++++++++++++++++++++-- include/like.php | 83 +---------------------------- mod/item.php | 42 +-------------- 3 files changed, 125 insertions(+), 124 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 4e1b300507..59bad946e2 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -2190,13 +2190,18 @@ class diaspora { * * @return string the handle in the format user@domain.tld */ - private function my_handle($me) { + private function my_handle($contact) { if ($contact["addr"] != "") return $contact["addr"]; // Normally we should have a filled "addr" field - but in the past this wasn't the case // So - just in case - we build the the address here. - return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); + if ($contact["nickname"] != "") + $nick = $contact["nickname"]; + else + $nick = $contact["nick"]; + + return $nick."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); } /** @@ -2689,7 +2694,7 @@ class diaspora { "guid" => $item["guid"], "target_type" => $target_type, "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, + "author_signature" => "", "diaspora_handle" => self::my_handle($owner))); } @@ -3052,5 +3057,118 @@ class diaspora { foreach($recips as $recip) self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); } + + /** + * @brief Stores the signature for likes that are created on our system + * + * @param array $contact The contact array of the "like" + * @param int $post_id The post id of the "like" + * + * @return bool Success + */ + function store_like_signature($contact, $post_id) { + + $enabled = intval(get_config('system','diaspora_enabled')); + if (!$enabled) { + logger('Diaspora support disabled, not storing like signature', LOGGER_DEBUG); + return false; + } + + // Is the contact the owner? Then fetch the private key + if (!$contact['self'] OR ($contact['uid'] == 0)) { + logger("No owner post, so not storing signature", LOGGER_DEBUG); + return false; + } + + $r = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval($contact['uid'])); + if(!$r) + return false; + + $contact["uprvkey"] = $r[0]['prvkey']; + + $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($post_id)); + if (!$r) + return false; + + if (!in_array($r[0]["verb"], array(ACTIVITY_LIKE, ACTIVITY_DISLIKE))) + return false; + + $message = self::construct_like($r[0], $contact); + $message["author_signature"] = self::signature($contact, $message); + + // In the future we will store the signature more flexible to support new fields. + // Right now we cannot change this since old Friendica versions (prior to 3.5) can only handle this format. + // (We are transmitting this data here via DFRN) + + $signed_text = $message["positive"].";".$message["guid"].";".$message["target_type"].";". + $message["parent_guid"].";".$message["diaspora_handle"]; + + q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')", + intval($post_id), + dbesc($signed_text), + dbesc($message["author_signature"]), + dbesc($message["diaspora_handle"]) + ); + + // This here will replace the lines above, once Diaspora changed its protocol + //q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + // intval($message_id), + // dbesc(json_encode($message)) + //); + + logger('Stored diaspora like signature'); + return true; + } + + /** + * @brief Stores the signature for comments that are created on our system + * + * @param array $item The item array of the comment + * @param array $contact The contact array of the item owner + * @param string $uprvkey The private key of the sender + * @param int $message_id The message id of the comment + * + * @return bool Success + */ + function store_comment_signature($item, $contact, $uprvkey, $message_id) { + + if ($uprvkey == "") { + logger('No private key, so not storing comment signature', LOGGER_DEBUG); + return false; + } + + $enabled = intval(get_config('system','diaspora_enabled')); + if (!$enabled) { + logger('Diaspora support disabled, not storing comment signature', LOGGER_DEBUG); + return false; + } + + $contact["uprvkey"] = $uprvkey; + + $message = self::construct_comment($item, $contact); + $message["author_signature"] = self::signature($contact, $message); + + // In the future we will store the signature more flexible to support new fields. + // Right now we cannot change this since old Friendica versions (prior to 3.5) can only handle this format. + // (We are transmitting this data here via DFRN) + $signed_text = $message["guid"].";".$message["parent_guid"].";". + $message["text"].";".$message["diaspora_handle"]; + + q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')", + intval($message_id), + dbesc($signed_text), + dbesc($message["author_signature"]), + dbesc($message["diaspora_handle"]) + ); + + // This here will replace the lines above, once Diaspora changed its protocol + //q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + // intval($message_id), + // dbesc(json_encode($message)) + //); + + logger('Stored diaspora comment signature'); + return true; + } } ?> diff --git a/include/like.php b/include/like.php index 2e5367e51e..49534ea613 100644 --- a/include/like.php +++ b/include/like.php @@ -1,4 +1,5 @@ <?php +require_once("include/diaspora.php"); /** * @brief add/remove activity to an item @@ -237,7 +238,7 @@ EOT; // Save the author information for the like in case we need to relay to Diaspora - store_diaspora_like_sig($activity, $post_type, $contact, $post_id); + diaspora::store_like_signature($contact, $post_id); $arr['id'] = $post_id; @@ -247,83 +248,3 @@ EOT; return true; } - -function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) { - // Note that we can only create a signature for a user of the local server. We don't have - // a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it - // means we are the relay, and for relayable_retractions, Diaspora - // only checks the parent_author_signature if it doesn't have to relay further - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('mod_like: diaspora support disabled, not storing like signature', LOGGER_DEBUG); - return; - } - - logger('mod_like: storing diaspora like signature'); - - if(($activity === ACTIVITY_LIKE) && ($post_type === t('status'))) { - // Only works for NETWORK_DFRN - $contact_baseurl_start = strpos($contact['url'],'://') + 3; - $contact_baseurl_length = strpos($contact['url'],'/profile') - $contact_baseurl_start; - $contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length); - $diaspora_handle = $contact['nick'] . '@' . $contact_baseurl; - - - // This code could never had worked (the return values form the queries were used in a wrong way. - // Additionally it is needlessly complicated. Either the contact is owner or not. And we have this data already. -/* - // Get contact's private key if he's a user of the local Friendica server - $r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1", - dbesc($contact['url']) - ); - - if( $r) { - $contact_uid = $r['uid']; - $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", - intval($contact_uid) - ); - - if( $r) - $contact_uprvkey = $r['prvkey']; - } -*/ - - // Is the contact the owner? Then fetch the private key - if ($contact['self'] AND ($contact['uid'] > 0)) { - $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", - intval($contact['uid']) - ); - - if($r) - $contact_uprvkey = $r[0]['prvkey']; - } - - $r = q("SELECT guid, parent FROM `item` WHERE id = %d LIMIT 1", - intval($post_id) - ); - if( $r) { - $p = q("SELECT guid FROM `item` WHERE id = %d AND parent = %d LIMIT 1", - intval($r[0]['parent']), - intval($r[0]['parent']) - ); - if( $p) { - $signed_text = 'true;'.$r[0]['guid'].';Post;'.$p[0]['guid'].';'.$diaspora_handle; - - if(isset($contact_uprvkey)) - $authorsig = base64_encode(rsa_sign($signed_text,$contact_uprvkey,'sha256')); - else - $authorsig = ''; - - q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($post_id), - dbesc($signed_text), - dbesc($authorsig), - dbesc($diaspora_handle) - ); - } - } - } - - return; -} diff --git a/mod/item.php b/mod/item.php index 2ade524a05..14c8203c98 100644 --- a/mod/item.php +++ b/mod/item.php @@ -24,6 +24,7 @@ require_once('include/threads.php'); require_once('include/text.php'); require_once('include/items.php'); require_once('include/Scrape.php'); +require_once('include/diaspora.php'); function item_post(&$a) { @@ -900,7 +901,7 @@ function item_post(&$a) { // Store the comment signature information in case we need to relay to Diaspora - store_diaspora_comment_sig($datarray, $author, ($self ? $user['prvkey'] : false), $parent_item, $post_id); + diaspora::store_comment_signature($datarray, $author, ($self ? $user['prvkey'] : false), $post_id); } else { $parent = $post_id; @@ -1245,42 +1246,3 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo return array('replaced' => $replaced, 'contact' => $r[0]); } - - -function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item, $post_id) { - // We won't be able to sign Diaspora comments for authenticated visitors - we don't have their private key - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('mod_item: diaspora support disabled, not storing comment signature', LOGGER_DEBUG); - return; - } - - - logger('mod_item: storing diaspora comment signature'); - - require_once('include/bb2diaspora.php'); - $signed_body = html_entity_decode(bb2diaspora($datarray['body'])); - - // Only works for NETWORK_DFRN - $contact_baseurl_start = strpos($author['url'],'://') + 3; - $contact_baseurl_length = strpos($author['url'],'/profile') - $contact_baseurl_start; - $contact_baseurl = substr($author['url'], $contact_baseurl_start, $contact_baseurl_length); - $diaspora_handle = $author['nick'] . '@' . $contact_baseurl; - - $signed_text = $datarray['guid'] . ';' . $parent_item['guid'] . ';' . $signed_body . ';' . $diaspora_handle; - - if( $uprvkey !== false ) - $authorsig = rsa_sign($signed_text,$uprvkey,'sha256'); - else - $authorsig = ''; - - q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($post_id), - dbesc($signed_text), - dbesc(base64_encode($authorsig)), - dbesc($diaspora_handle) - ); - - return; -} From 186eaf1264320de3e5604d09b7ae7818ba5c4465 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 20 Mar 2016 10:30:06 +0100 Subject: [PATCH 217/273] Take the second largest picture as preview - not the smallest one --- mod/fbrowser.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mod/fbrowser.php b/mod/fbrowser.php index 0a2a7dead5..5836efbe52 100644 --- a/mod/fbrowser.php +++ b/mod/fbrowser.php @@ -74,10 +74,18 @@ function fbrowser_content($a){ $filename_e = $rr['filename']; } + // Take the second largest picture as preview + $p = q("SELECT `scale` FROM `photo` WHERE `resource-id` = '%s' AND `scale` > %d ORDER BY `resource-id`, `scale` LIMIT 1", + dbesc($rr['resource-id']), intval($rr['hiq'])); + if ($p) + $scale = $p[0]["scale"]; + else + $scale = $rr['loq']; + return array( $a->get_baseurl() . '/photos/' . $a->user['nickname'] . '/image/' . $rr['resource-id'], $filename_e, - $a->get_baseurl() . '/photo/' . $rr['resource-id'] . '-' . $rr['loq'] . '.'. $ext + $a->get_baseurl() . '/photo/' . $rr['resource-id'] . '-' . $scale . '.'. $ext ); } $files = array_map("_map_files1", $r); From 8f00836ffb47758fa16101cafeb1a03d13d88de4 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 20 Mar 2016 15:01:50 +0100 Subject: [PATCH 218/273] Add the guid to items that we create locally --- include/like.php | 1 + mod/dfrn_confirm.php | 3 ++- mod/mood.php | 2 +- mod/photos.php | 5 +++-- mod/poke.php | 1 + mod/profiles.php | 2 ++ mod/subthread.php | 5 +++-- mod/tagger.php | 5 +++-- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/include/like.php b/include/like.php index 646e0727be..d852cc3b31 100644 --- a/include/like.php +++ b/include/like.php @@ -196,6 +196,7 @@ EOT; $arr = array(); + $arr['guid'] = get_guid(32); $arr['uri'] = $uri; $arr['uid'] = $owner_uid; $arr['contact-id'] = $contact['id']; diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php index 68950ec285..aed9809ca2 100644 --- a/mod/dfrn_confirm.php +++ b/mod/dfrn_confirm.php @@ -448,6 +448,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) { if(count($self)) { $arr = array(); + $arr['guid'] = get_guid(32); $arr['uri'] = $arr['parent-uri'] = item_new_uri($a->get_hostname(), $uid); $arr['uid'] = $uid; $arr['contact-id'] = $self[0]['id']; @@ -466,7 +467,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) { $BPhoto = '[url=' . $contact['url'] . ']' . '[img]' . $contact['thumb'] . '[/img][/url]'; $arr['verb'] = ACTIVITY_FRIEND; - $arr['object-type'] = ACTIVITY_OBJ_PERSON; + $arr['object-type'] = ACTIVITY_OBJ_PERSON; $arr['body'] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$BPhoto; $arr['object'] = '<object><type>' . ACTIVITY_OBJ_PERSON . '</type><title>' . $contact['name'] . '</title>' diff --git a/mod/mood.php b/mod/mood.php index eee11e20c5..5e6ca0fcfc 100644 --- a/mod/mood.php +++ b/mod/mood.php @@ -62,7 +62,7 @@ function mood_init(&$a) { $action = sprintf( t('%1$s is currently %2$s'), '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' , $verbs[$verb]); $arr = array(); - + $arr['guid'] = get_guid(32); $arr['uid'] = $uid; $arr['uri'] = $uri; $arr['parent-uri'] = (($parent_uri) ? $parent_uri : $uri); diff --git a/mod/photos.php b/mod/photos.php index 2257a96653..4761b627d8 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -488,7 +488,7 @@ function photos_post(&$a) { $uri = item_new_uri($a->get_hostname(),$page_owner_uid); $arr = array(); - + $arr['guid'] = get_guid(32); $arr['uid'] = $page_owner_uid; $arr['uri'] = $uri; $arr['parent-uri'] = $uri; @@ -677,7 +677,7 @@ function photos_post(&$a) { $uri = item_new_uri($a->get_hostname(),$page_owner_uid); $arr = array(); - + $arr['guid'] = get_guid(32); $arr['uid'] = $page_owner_uid; $arr['uri'] = $uri; $arr['parent-uri'] = $uri; @@ -904,6 +904,7 @@ function photos_post(&$a) { if($lat && $lon) $arr['coord'] = $lat . ' ' . $lon; + $arr['guid'] = get_guid(32); $arr['uid'] = $page_owner_uid; $arr['uri'] = $uri; $arr['parent-uri'] = $uri; diff --git a/mod/poke.php b/mod/poke.php index 45a577cda6..4a643435be 100644 --- a/mod/poke.php +++ b/mod/poke.php @@ -91,6 +91,7 @@ function poke_init(&$a) { $arr = array(); + $arr['guid'] = get_guid(32); $arr['uid'] = $uid; $arr['uri'] = $uri; $arr['parent-uri'] = (($parent_uri) ? $parent_uri : $uri); diff --git a/mod/profiles.php b/mod/profiles.php index 0b8261422f..39382fbdd5 100644 --- a/mod/profiles.php +++ b/mod/profiles.php @@ -526,6 +526,8 @@ function profile_activity($changed, $value) { return; $arr = array(); + + $arr['guid'] = get_guid(32); $arr['uri'] = $arr['parent-uri'] = item_new_uri($a->get_hostname(), local_user()); $arr['uid'] = local_user(); $arr['contact-id'] = $self[0]['id']; diff --git a/mod/subthread.php b/mod/subthread.php index 1486a33b42..33cf7489c1 100644 --- a/mod/subthread.php +++ b/mod/subthread.php @@ -103,10 +103,11 @@ EOT; $bodyverb = t('%1$s is following %2$s\'s %3$s'); if(! isset($bodyverb)) - return; + return; $arr = array(); + $arr['guid'] = get_guid(32); $arr['uri'] = $uri; $arr['uid'] = $owner_uid; $arr['contact-id'] = $contact['id']; @@ -123,7 +124,7 @@ EOT; $arr['author-name'] = $contact['name']; $arr['author-link'] = $contact['url']; $arr['author-avatar'] = $contact['thumb']; - + $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; $plink = '[url=' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . ']' . $post_type . '[/url]'; diff --git a/mod/tagger.php b/mod/tagger.php index 2c469a58bb..26166a3cc0 100644 --- a/mod/tagger.php +++ b/mod/tagger.php @@ -95,12 +95,13 @@ EOT; $bodyverb = t('%1$s tagged %2$s\'s %3$s with %4$s'); if(! isset($bodyverb)) - return; + return; $termlink = html_entity_decode('⌗') . '[url=' . $a->get_baseurl() . '/search?tag=' . urlencode($term) . ']'. $term . '[/url]'; $arr = array(); + $arr['guid'] = get_guid(32); $arr['uri'] = $uri; $arr['uid'] = $owner_uid; $arr['contact-id'] = $contact['id']; @@ -115,7 +116,7 @@ EOT; $arr['author-name'] = $contact['name']; $arr['author-link'] = $contact['url']; $arr['author-avatar'] = $contact['thumb']; - + $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; $alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]'; $plink = '[url=' . $item['plink'] . ']' . $post_type . '[/url]'; From 77ed71e2e07ebbea9295308ba02fbc7e75ff927d Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 20 Mar 2016 15:53:37 +0100 Subject: [PATCH 219/273] DFRN: Remote tagging works now --- include/dfrn.php | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index 1c5ac2b012..d96805a56b 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -85,7 +85,7 @@ class dfrn { $converse = true; if($a->argv[$x] == 'starred') $starred = true; - if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1])) + if($a->argv[$x] == 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1])) $category = $a->argv[$x+1]; } } @@ -244,7 +244,7 @@ class dfrn { foreach($items as $item) { // prevent private email from leaking. - if($item['network'] === NETWORK_MAIL) + if($item['network'] == NETWORK_MAIL) continue; // public feeds get html, our own nodes use bbcode @@ -628,7 +628,7 @@ class dfrn { if($r->title) xml_add_element($doc, $entry, "title", $r->title); if($r->link) { - if(substr($r->link,0,1) === '<') { + if(substr($r->link,0,1) == '<') { if(strstr($r->link,'&') && (! strstr($r->link,'&'))) $r->link = str_replace('&','&', $r->link); @@ -759,7 +759,7 @@ class dfrn { // The "content" field is not read by the receiver. We could remove it when the type is "text" // We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env" - xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type)); + xml_add_element($doc, $entry, "content", (($type == 'html') ? $htmlbody : $body), array("type" => $type)); // 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", @@ -1773,6 +1773,9 @@ class dfrn { * @return bool Should the processing of the entries be continued? */ private function process_verbs($entrytype, $importer, &$item, &$is_like) { + + logger("Process verb ".$item["verb"]." and object-type ".$item["object-type"]." for entrytype ".$entrytype, LOGGER_DEBUG); + if (($entrytype == DFRN_TOP_LEVEL)) { // The filling of the the "contact" variable is done for legcy reasons // The functions below are partly used by ostatus.php as well - where we have this variable @@ -1803,11 +1806,11 @@ class dfrn { return false; } } else { - if(($item["verb"] === ACTIVITY_LIKE) - || ($item["verb"] === ACTIVITY_DISLIKE) - || ($item["verb"] === ACTIVITY_ATTEND) - || ($item["verb"] === ACTIVITY_ATTENDNO) - || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) { + if(($item["verb"] == ACTIVITY_LIKE) + || ($item["verb"] == ACTIVITY_DISLIKE) + || ($item["verb"] == ACTIVITY_ATTEND) + || ($item["verb"] == ACTIVITY_ATTENDNO) + || ($item["verb"] == ACTIVITY_ATTENDMAYBE)) { $is_like = true; $item["type"] = "activity"; $item["gravity"] = GRAVITY_LIKE; @@ -1833,7 +1836,7 @@ class dfrn { } else $is_like = false; - if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + if(($item["verb"] == ACTIVITY_TAG) && ($item["object-type"] == ACTIVITY_OBJ_TAGTERM)) { $xo = parse_xml_string($item["object"],false); $xt = parse_xml_string($item["target"],false); @@ -2261,15 +2264,17 @@ class dfrn { else return; - if($item["object-type"] === ACTIVITY_OBJ_EVENT) { + if($item["object-type"] == ACTIVITY_OBJ_EVENT) { logger("Deleting event ".$item["event-id"], LOGGER_DEBUG); event_delete($item["event-id"]); } - if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + if(($item["verb"] == ACTIVITY_TAG) && ($item["object-type"] == ACTIVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($item["object"],false); $xt = parse_xml_string($item["target"],false); - if($xt->type === ACTIVITY_OBJ_NOTE) { + + if($xt->type == ACTIVITY_OBJ_NOTE) { $i = q("SELECT `id`, `contact-id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($xt->id), intval($importer["importer_uid"]) From 5a04ba84164070855c26737640f41ed3cb6dcf11 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 20 Mar 2016 16:16:15 +0100 Subject: [PATCH 220/273] Added documentation --- include/xml.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/xml.php b/include/xml.php index c74c23c473..2aed3fe8ed 100644 --- a/include/xml.php +++ b/include/xml.php @@ -4,6 +4,16 @@ * */ class xml { + /** + * @brief Creates an XML structure out of a given array + * + * @param array $array The array of the XML structure that will be generated + * @param object $xml The createdXML will be returned by reference + * @param bool $remove_header Should the XML header be removed or not? + * @param array $namespaces List of namespaces + * + * @return string The created XML + */ function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) { if ($root) { @@ -60,6 +70,13 @@ class xml { } } + /** + * @brief Copies an XML object + * + * @param object $source The XML source + * @param object $target The XML target + * @param string $elementname Name of the XML element of the target + */ function copy(&$source, &$target, $elementname) { if (count($source->children()) == 0) $target->addChild($elementname, xmlify($source)); From c284ab5eff79fa2a87f7214fe433001712db08c9 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 21 Mar 2016 19:58:45 +0100 Subject: [PATCH 221/273] Some more documentation - to make @rabuzarus happy --- include/diaspora.php | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 59bad946e2..6f30ab9247 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -116,7 +116,7 @@ class diaspora { * @param string $handle The handle of the signature owner * @param integer $level This value is only set inside this function to avoid endless loops * - * @return the repaired signature + * @return string the repaired signature */ function repair_signature($signature, $handle = "", $level = 1) { @@ -179,16 +179,6 @@ class diaspora { $decrypted = pkcs5_unpad($decrypted); - /** - * $decrypted now contains something like - * - * <decrypted_header> - * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv> - * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key> - * <author_id>galaxor@diaspora.priateship.org</author_id> - * </decrypted_header> - */ - logger('decrypted: '.$decrypted, LOGGER_DEBUG); $idom = parse_xml_string($decrypted,false); @@ -795,7 +785,7 @@ class diaspora { } /** - * @brief sub function of "fetch_guid" + * @brief sub function of "fetch_guid" which checks for links in messages * * @param array $match array containing a link that has to be checked for a message link * @param array $item The item array @@ -838,7 +828,10 @@ class diaspora { * @param string $server The url of the server * @param int $level Endless loop prevention * - * @return array of message, author and public key + * @return array + * 'message' => The message XML + * 'author' => The author handle + * 'key' => The public key of the author */ private function message($guid, $server, $level = 0) { From 90d4b9342452345671672ed301c66e842a74aa24 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 22 Mar 2016 07:13:56 +0100 Subject: [PATCH 222/273] Avoid an empty handle --- include/diaspora.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/diaspora.php b/include/diaspora.php index 6f30ab9247..632e3782c7 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -647,7 +647,7 @@ class diaspora { if($contact['addr'] != "") $handle = $contact['addr']; - elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { + else { $baseurl_start = strpos($contact['url'],'://') + 3; $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); From 62103fe5f42363d14e33b127c5ccaf2ba176d6ca Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 22 Mar 2016 23:00:42 +0100 Subject: [PATCH 223/273] Reshare of reshares now work. --- include/diaspora.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 632e3782c7..289f717708 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1853,10 +1853,14 @@ class diaspora { logger("reshared message ".$guid." already exists on system."); // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - if (self::is_reshare($r[0]["body"], false)) + // Then refetch the content, if it is a reshare from a reshare. + // If it is a reshared post from another network then reformat to avoid display problems with two share elements + if (self::is_reshare($r[0]["body"], true)) $r = array(); - else + elseif (self::is_reshare($r[0]["body"], false)) { + $r[0]["body"] = diaspora2bb(bb2diaspora($r[0]["body"])); + return $r[0]; + } else return $r[0]; } From 791ce24cd52d118391fd9c6a1b0a2a10efb6240f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 22 Mar 2016 23:24:07 +0100 Subject: [PATCH 224/273] Bugfix: Avoid warning with non object OEmbed data --- include/bbcode.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/bbcode.php b/include/bbcode.php index c1156e3afe..8545b2ff82 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -311,6 +311,9 @@ function tryoembed($match){ $o = oembed_fetch_url($url); + if (!is_object($o)) + return $match[0]; + if (isset($match[2])) $o->title = $match[2]; From 28dfaa694c2203163eee6d61b724dede4590c099 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 23 Mar 2016 07:36:17 +0100 Subject: [PATCH 225/273] Add OEmbed data to the body of reshares --- include/diaspora.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/diaspora.php b/include/diaspora.php index 289f717708..b339e73157 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1859,6 +1859,10 @@ class diaspora { $r = array(); elseif (self::is_reshare($r[0]["body"], false)) { $r[0]["body"] = diaspora2bb(bb2diaspora($r[0]["body"])); + + // Add OEmbed and other information to the body + $r[0]["body"] = add_page_info_to_body($r[0]["body"], false, true); + return $r[0]; } else return $r[0]; From ac35f8c756a85cd36c8c2c2df7b7306056443f49 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 23 Mar 2016 09:22:59 +0100 Subject: [PATCH 226/273] If the message already exists then the message id should be returned --- include/diaspora.php | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index b339e73157..308a799118 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -755,8 +755,8 @@ class diaspora { * * @param int $uid The user id * @param string $guid The guid of the message - * - * @return bool "true" if the message already was stored into the system +y * + * @return int|bool message id if the message already was stored into the system - or false. */ private function message_exists($uid, $guid) { $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", @@ -766,7 +766,7 @@ class diaspora { if($r) { logger("message ".$guid." already exists for user ".$uid); - return true; + return $r[0]["id"]; } return false; @@ -1028,8 +1028,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) @@ -1357,8 +1358,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) @@ -1926,8 +1928,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $original_item = self::original_item($root_guid, $root_author, $author); if (!$original_item) @@ -2110,8 +2113,9 @@ class diaspora { if (!$contact) return false; - if (self::message_exists($importer["uid"], $guid)) - return false; + $message_id = self::message_exists($importer["uid"], $guid); + if ($message_id) + return $message_id; $address = array(); if ($data->location) From 799ff777977cb46485c46a659848f92772aadf77 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 23 Mar 2016 10:24:01 +0100 Subject: [PATCH 227/273] API: Support for the conversation api call from GNU Social --- doc/api.md | 12 ++++++++++++ include/api.php | 1 + 2 files changed, 13 insertions(+) diff --git a/doc/api.md b/doc/api.md index bf287585d3..7d6f440c58 100644 --- a/doc/api.md +++ b/doc/api.md @@ -388,6 +388,18 @@ Friendica doesn't allow showing friends of other users. --- ### 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 (*) diff --git a/include/api.php b/include/api.php index 699b066d25..a494e3cdd9 100644 --- a/include/api.php +++ b/include/api.php @@ -1550,6 +1550,7 @@ return api_apply_template("timeline", $type, $data); } api_register_func('api/conversation/show','api_conversation_show', true); + api_register_func('api/statusnet/conversation','api_conversation_show', true); /** From 7aae852fe0285ac7dd88685f92715691a099190d Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 23 Mar 2016 22:12:08 +0100 Subject: [PATCH 228/273] Some more code cleaning --- include/diaspora.php | 35 ++++++++++++++++++++++++++--------- include/xml.php | 1 + 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 308a799118..a608516622 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1694,11 +1694,8 @@ y * $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]"; $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto; - $arr["object"] = "<object><type>".ACTIVITY_OBJ_PERSON."</type><title>".$contact["name"]."</title>" - ."<id>".$contact["url"]."/".$contact["name"]."</id>"; - $arr["object"] .= "<link>".xmlify('<link rel="alternate" type="text/html" href="'.$contact["url"].'" />'."\n"); - $arr["object"] .= xmlify('<link rel="photo" type="image/jpeg" href="'.$contact["thumb"].'" />'."\n"); - $arr["object"] .= "</link></object>\n"; + $arr["object"] = self::construct_new_friend_object($contact); + $arr["last-child"] = 1; $arr["allow_cid"] = $user[0]["allow_cid"]; @@ -1713,6 +1710,26 @@ y * } } + /** + * @brief Creates a XML object for a "new friend" message + * + * @param array $contact Array of the contact + * + * @return string The XML + */ + private function construct_new_friend_object($contact) { + $objtype = ACTIVITY_OBJ_PERSON; + $link = '<link rel="alternate" type="text/html" href="'.$contact["url"].'" />'."\n". + '<link rel="photo" type="image/jpeg" href="'.$contact["thumb"].'" />'."\n"; + + $xmldata = array("object" => array("type" => $objtype, + "title" => $contact["name"], + "id" => $contact["url"]."/".$contact["name"], + "link" => $link)); + + return xml::from_array($xmldata, $xml, true); + } + /** * @brief Processes incoming sharing notification * @@ -2184,9 +2201,9 @@ y * return $message_id; } - /****************************************************************************************** + /* ************************************************************************************** * * Here are all the functions that are needed to transmit data with the Diaspora protocol * - ******************************************************************************************/ + * ************************************************************************************** */ /** * @brief returnes the handle of a contact @@ -3071,7 +3088,7 @@ y * * * @return bool Success */ - function store_like_signature($contact, $post_id) { + public static function store_like_signature($contact, $post_id) { $enabled = intval(get_config('system','diaspora_enabled')); if (!$enabled) { @@ -3135,7 +3152,7 @@ y * * * @return bool Success */ - function store_comment_signature($item, $contact, $uprvkey, $message_id) { + public static function store_comment_signature($item, $contact, $uprvkey, $message_id) { if ($uprvkey == "") { logger('No private key, so not storing comment signature', LOGGER_DEBUG); diff --git a/include/xml.php b/include/xml.php index 2aed3fe8ed..47a2f6f7d5 100644 --- a/include/xml.php +++ b/include/xml.php @@ -1,5 +1,6 @@ <?php /** + * @file include/xml.php * @brief This class contain functions to work with XML data * */ From 24afcdd5dd6354fb34a37aec82b6144f4a76840c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 24 Mar 2016 08:35:06 +0100 Subject: [PATCH 229/273] Scrape: Always take the first alias --- include/Scrape.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Scrape.php b/include/Scrape.php index e8e9a97a16..03d21047e7 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -444,7 +444,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { if($link['@attributes']['rel'] === 'alias') { if(strpos($link['@attributes']['href'],'@') === false) { if(isset($profile)) { - if($link['@attributes']['href'] !== $profile) + if(($link['@attributes']['href'] !== $profile) AND ($alias == "")) $alias = unamp($link['@attributes']['href']); } else From 7bf079d858db1604b9c2a34a414756c1b70066de Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 24 Mar 2016 15:59:53 +0100 Subject: [PATCH 230/273] Some more documentation - again. --- include/diaspora.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/diaspora.php b/include/diaspora.php index a608516622..c5d6943e59 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -755,7 +755,7 @@ class diaspora { * * @param int $uid The user id * @param string $guid The guid of the message -y * + * * @return int|bool message id if the message already was stored into the system - or false. */ private function message_exists($uid, $guid) { @@ -2143,6 +2143,7 @@ y * $datarray = array(); + // Attach embedded pictures to the body if ($data->photo) { foreach ($data->photo AS $photo) $body = "[img]".unxmlify($photo->remote_photo_path). From 7e7cac19f62cef5fe52660a9312ac01a7d3f74a2 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 24 Mar 2016 21:32:55 +0100 Subject: [PATCH 231/273] Avoid a guid whith spaces. --- include/diaspora.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/diaspora.php b/include/diaspora.php index c5d6943e59..32190bc7d6 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -2611,8 +2611,9 @@ class diaspora { $link = $matches[1]; $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); - if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) + if (($ret["root_guid"] == $link) OR (trim($ret["root_guid"]) == "")) return(false); + return($ret); } From 16ef9f49408050aab2b6a180f7df9103145f0c03 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 25 Mar 2016 00:49:18 +0100 Subject: [PATCH 232/273] Support for the new contact request data type --- include/diaspora.php | 50 +++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 32190bc7d6..277eb6f8e8 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -339,6 +339,9 @@ class diaspora { case "comment": return self::receive_comment($importer, $sender, $fields, $msg["message"]); + case "contact": + return self::receive_contact_request($importer, $fields); + case "conversation": return self::receive_conversation($importer, $msg, $fields); @@ -360,9 +363,6 @@ class diaspora { case "profile": return self::receive_profile($importer, $fields); - case "request": - return self::receive_request($importer, $fields); - case "reshare": return self::receive_reshare($importer, $fields, $msg["message"]); @@ -418,6 +418,9 @@ class diaspora { if (in_array($type, array("signed_retraction", "relayable_retraction"))) $type = "retraction"; + if ($type == "request") + $type = "contact"; + $fields = new SimpleXMLElement("<".$type."/>"); $signed_data = ""; @@ -1377,7 +1380,7 @@ class diaspora { // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora // We would accept this anyhow. - if ($positive === "true") + if ($positive == "true") $verb = ACTIVITY_LIKE; else $verb = ACTIVITY_DISLIKE; @@ -1738,22 +1741,43 @@ class diaspora { * * @return bool Success */ - private function receive_request($importer, $data) { + private function receive_contact_request($importer, $data) { $author = unxmlify($data->author); $recipient = unxmlify($data->recipient); if (!$author || !$recipient) return false; + // the current protocol version doesn't know these fields + // That means that we will assume their existance + if (isset($data->following)) + $following = (unxmlify($data->following) == "true"); + else + $following = true; + + if (isset($data->sharing)) + $sharing = (unxmlify($data->sharing) == "true"); + else + $sharing = true; + $contact = self::contact_by_handle($importer["uid"],$author); - if($contact) { + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + if ($contact) { + if ($following AND $sharing) { + self::receive_request_make_friend($importer, $contact); + return true; + } else /// @todo Handle all possible variations of adding and retracting of permissions + return false; + } - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - - self::receive_request_make_friend($importer, $contact); - return true; + if (!$following AND $sharing AND in_array($importer["page-flags"], array(PAGE_SOAPBOX, PAGE_NORMAL))) { + logger("Author ".$author." wants to share with us - but doesn't want to listen. Request is ignored.", LOGGER_DEBUG); + return false; + } elseif (!$following AND !$sharing) { + logger("Author ".$author." doesn't want anything - and we don't know the author. Request is ignored.", LOGGER_DEBUG); + return false; } $ret = self::person_by_handle($author); @@ -1824,8 +1848,10 @@ class diaspora { // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX // we are going to change the relationship and make them a follower. - if($importer["page-flags"] == PAGE_FREELOVE) + if (($importer["page-flags"] == PAGE_FREELOVE) AND $sharing AND $following) $new_relation = CONTACT_IS_FRIEND; + elseif (($importer["page-flags"] == PAGE_FREELOVE) AND $sharing) + $new_relation = CONTACT_IS_SHARING; else $new_relation = CONTACT_IS_FOLLOWER; From faa9b77a90be70f76990451aff9c25fa2e05dad4 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 27 Mar 2016 23:25:32 +0200 Subject: [PATCH 233/273] Some more small documentation stuff --- include/diaspora.php | 4 ++-- include/xml.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 277eb6f8e8..d2a90fc983 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -118,7 +118,7 @@ class diaspora { * * @return string the repaired signature */ - function repair_signature($signature, $handle = "", $level = 1) { + private function repair_signature($signature, $handle = "", $level = 1) { if ($signature == "") return ($signature); @@ -146,7 +146,7 @@ class diaspora { * 'author' -> author diaspora handle * 'key' -> author public key (converted to pkcs#8) */ - function decode($importer, $xml) { + public static function decode($importer, $xml) { $public = false; $basedom = parse_xml_string($xml); diff --git a/include/xml.php b/include/xml.php index 47a2f6f7d5..2bcc73b8f7 100644 --- a/include/xml.php +++ b/include/xml.php @@ -15,7 +15,7 @@ class xml { * * @return string The created XML */ - function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) { + public static function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) { if ($root) { foreach($array as $key => $value) { @@ -78,7 +78,7 @@ class xml { * @param object $target The XML target * @param string $elementname Name of the XML element of the target */ - function copy(&$source, &$target, $elementname) { + public static function copy(&$source, &$target, $elementname) { if (count($source->children()) == 0) $target->addChild($elementname, xmlify($source)); else { From 7e711d9d62ff9163d7cc83e24bae556e82f7fef8 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 27 Mar 2016 23:38:35 +0200 Subject: [PATCH 234/273] And some more doc stuff --- include/diaspora.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index d2a90fc983..3795def479 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -737,7 +737,7 @@ class diaspora { * @param string $handle The checked handle in the format user@domain.tld * @param bool $is_comment Is the check for a comment? * - * @return bool is posting allowed? + * @return array The contact data */ private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { $contact = self::contact_by_handle($importer["uid"], $handle); @@ -931,7 +931,9 @@ class diaspora { * @param array $person The record of the person * @param int $uid The user id * - * @return array of contact id and network type + * @return array + * 'cid' => contact id + * 'network' => network type */ private function author_contact_by_url($contact, $person, $uid) { From b6121a0009340a15e7d0317803707d4c11197a6d Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 28 Mar 2016 00:06:46 +0200 Subject: [PATCH 235/273] And wow ... some more documentation --- include/xml.php | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xml.php b/include/xml.php index 2bcc73b8f7..aa74cf65cf 100644 --- a/include/xml.php +++ b/include/xml.php @@ -12,6 +12,7 @@ class xml { * @param object $xml The createdXML will be returned by reference * @param bool $remove_header Should the XML header be removed or not? * @param array $namespaces List of namespaces + * @param bool $root - interally used parameter. Mustn't be used from outside. * * @return string The created XML */ From 3c24f43011b3f00bf1ee6e494f2af2d06cd68b51 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 28 Mar 2016 22:21:14 +0200 Subject: [PATCH 236/273] Guesss what? Yeah, some documentation --- util/createdoxygen.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) mode change 100755 => 100644 util/createdoxygen.php diff --git a/util/createdoxygen.php b/util/createdoxygen.php old mode 100755 new mode 100644 index d48114b671..163c94bb97 --- a/util/createdoxygen.php +++ b/util/createdoxygen.php @@ -1,5 +1,10 @@ #!/usr/bin/php <?php +/** + * @file util/createdoxygen.php + * @brief Adds a doxygen header to functions + */ + if (count($_SERVER["argv"]) < 2) die("usage: createdoxygen.php file\n"); @@ -45,6 +50,13 @@ foreach ($lines AS $line) { $previous = $line; } +/** + * @brief Adds a doxygen header + * + * @param string $line The current line of the document + * + * @return string added doxygen header + */ function add_documentation($line) { $trimmed = ltrim($line); From f80f4f6a97cea623ee53a596069a96fbcf147c8d Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Mon, 28 Mar 2016 22:35:11 +0200 Subject: [PATCH 237/273] Some changed doxygen header stuff --- include/diaspora.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 3795def479..e3a3dcd78c 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -277,7 +277,7 @@ class diaspora { * * @param array $msg The post that will be dispatched * - * @return bool Was the message accepted? + * @return int The message id of the generated message, "true" or "false" if there was an error */ public static function dispatch_public($msg) { @@ -289,7 +289,7 @@ class diaspora { // Use a dummy importer to import the data for the public copy $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); - $item_id = self::dispatch($importer,$msg); + $message_id = self::dispatch($importer,$msg); // Now distribute it to the followers $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN @@ -306,7 +306,7 @@ class diaspora { } else logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); - return $item_id; + return $message_id; } /** @@ -315,7 +315,7 @@ class diaspora { * @param array $importer Array of the importer user * @param array $msg The post that will be dispatched * - * @return bool Was the message accepted? + * @return int The message id of the generated message, "true" or "false" if there was an error */ public static function dispatch($importer, $msg) { From 4d668fac8b35459c7d8d3edf717632f807cc24fa Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 29 Mar 2016 17:54:36 +0200 Subject: [PATCH 238/273] Not sure if that is correct ... --- include/xml.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/xml.php b/include/xml.php index aa74cf65cf..a454e61566 100644 --- a/include/xml.php +++ b/include/xml.php @@ -1,6 +1,10 @@ <?php /** * @file include/xml.php + */ + + +/** * @brief This class contain functions to work with XML data * */ From b93e1d73a1a3823fbb3fd317a85be17e8fad1d3c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 30 Mar 2016 12:43:15 +0200 Subject: [PATCH 239/273] New OStatus implementation --- include/items.php | 6 +- include/notifier.php | 2 +- include/ostatus.php | 1 + include/ostatus2.php | 1778 +++++++++++++++++++++++++++++++++++++ include/pubsubpublish.php | 2 +- mod/salmon.php | 4 +- 6 files changed, 1786 insertions(+), 7 deletions(-) create mode 100644 include/ostatus2.php diff --git a/include/items.php b/include/items.php index f8c3149d58..233d72d133 100644 --- a/include/items.php +++ b/include/items.php @@ -383,9 +383,9 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa // Converting the plink if ($arr['network'] == NETWORK_OSTATUS) { if (isset($arr['plink'])) - $arr['plink'] = ostatus_convert_href($arr['plink']); + $arr['plink'] = ostatus::convert_href($arr['plink']); elseif (isset($arr['uri'])) - $arr['plink'] = ostatus_convert_href($arr['uri']); + $arr['plink'] = ostatus::convert_href($arr['uri']); } if(x($arr, 'gravity')) @@ -1243,7 +1243,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) //$tempfile = tempnam(get_temppath(), "ostatus2"); //file_put_contents($tempfile, $xml); logger("Consume OStatus messages ", LOGGER_DEBUG); - ostatus_import($xml,$importer,$contact, $hub); + ostatus::import($xml,$importer,$contact, $hub); } return; } diff --git a/include/notifier.php b/include/notifier.php index a46744f070..18a617ac2f 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -223,7 +223,7 @@ function notifier_run(&$argv, &$argc){ if(! ($mail || $fsuggest || $relocate)) { - $slap = ostatus_salmon($target_item,$owner); + $slap = ostatus::salmon($target_item,$owner); require_once('include/group.php'); diff --git a/include/ostatus.php b/include/ostatus.php index 5ba9f0e83c..02447e3ade 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -12,6 +12,7 @@ require_once("include/Scrape.php"); require_once("include/follow.php"); require_once("include/api.php"); require_once("mod/proxy.php"); +require_once("include/ostatus2.php"); define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes diff --git a/include/ostatus2.php b/include/ostatus2.php new file mode 100644 index 0000000000..53c279f254 --- /dev/null +++ b/include/ostatus2.php @@ -0,0 +1,1778 @@ +<?php +require_once("include/Contact.php"); +require_once("include/threads.php"); +require_once("include/html2bbcode.php"); +require_once("include/bbcode.php"); +require_once("include/items.php"); +require_once("mod/share.php"); +require_once("include/enotify.php"); +require_once("include/socgraph.php"); +require_once("include/Photo.php"); +require_once("include/Scrape.php"); +require_once("include/follow.php"); +require_once("include/api.php"); +require_once("mod/proxy.php"); + +class xml2 { + public static function create_element($doc, $element, $value = "", $attributes = array()) { + $element = $doc->createElement($element, xmlify($value)); + + foreach ($attributes AS $key => $value) { + $attribute = $doc->createAttribute($key); + $attribute->value = xmlify($value); + $element->appendChild($attribute); + } + return $element; + } + + public static function add_element($doc, $parent, $element, $value = "", $attributes = array()) { + $element = self::create_element($doc, $element, $value, $attributes); + $parent->appendChild($element); + } +} + +class ostatus { + const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes + const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes + const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes + + private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) { + + $author = array(); + $author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; + $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue; + + // Preserve the value + $authorlink = $author["author-link"]; + + $alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes; + if (is_object($alternate)) + foreach($alternate AS $attributes) + if ($attributes->name == "href") + $author["author-link"] = $attributes->textContent; + + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", + intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), + dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET)); + if ($r) { + $contact = $r[0]; + $author["contact-id"] = $r[0]["id"]; + } else + $author["contact-id"] = $contact["id"]; + + $avatarlist = array(); + $avatars = $xpath->query("atom:author/atom:link[@rel='avatar']", $context); + foreach($avatars AS $avatar) { + $href = ""; + $width = 0; + foreach($avatar->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "width") + $width = $attributes->textContent; + } + if (($width > 0) AND ($href != "")) + $avatarlist[$width] = $href; + } + if (count($avatarlist) > 0) { + krsort($avatarlist); + $author["author-avatar"] = current($avatarlist); + } + + $displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($displayname != "") + $author["author-name"] = $displayname; + + $author["owner-name"] = $author["author-name"]; + $author["owner-link"] = $author["author-link"]; + $author["owner-avatar"] = $author["author-avatar"]; + + // Only update the contacts if it is an OStatus contact + if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { + // Update contact data + + $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; + if ($value != "") + $contact["notify"] = $value; + + $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["alias"] = $value; + + $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; + + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; + + $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["about"] = html2bbcode($value); + + $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; + + if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { + + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); + + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), + dbesc(datetime_convert()), intval($contact["id"])); + + poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], + "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); + } + + if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) { + logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); + + update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); + } + + $contact["generation"] = 2; + $contact["photo"] = $author["author-avatar"]; + update_gcontact($contact); + } + + return($author); + } + + public static function salmon_author($xml, $importer) { + + if ($xml == "") + return; + + $doc = new DOMDocument(); + @$doc->loadXML($xml); + + $xpath = new DomXPath($doc); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); + + $entries = $xpath->query('/atom:entry'); + + foreach ($entries AS $entry) { + // fetch the author + $author = self::fetchauthor($xpath, $entry, $importer, $contact, true); + return $author; + } + } + + public static function import($xml,$importer,&$contact, &$hub) { + + logger("Import OStatus message", LOGGER_DEBUG); + + if ($xml == "") + return; + + //$tempfile = tempnam(get_temppath(), "import"); + //file_put_contents($tempfile, $xml); + + $doc = new DOMDocument(); + @$doc->loadXML($xml); + + $xpath = new DomXPath($doc); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); + + $gub = ""; + $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes; + if (is_object($hub_attributes)) + foreach($hub_attributes AS $hub_attribute) + if ($hub_attribute->name == "href") { + $hub = $hub_attribute->textContent; + logger("Found hub ".$hub, LOGGER_DEBUG); + } + + $header = array(); + $header["uid"] = $importer["uid"]; + $header["network"] = NETWORK_OSTATUS; + $header["type"] = "remote"; + $header["wall"] = 0; + $header["origin"] = 0; + $header["gravity"] = GRAVITY_PARENT; + + // it could either be a received post or a post we fetched by ourselves + // depending on that, the first node is different + $first_child = $doc->firstChild->tagName; + + if ($first_child == "feed") + $entries = $xpath->query('/atom:feed/atom:entry'); + else + $entries = $xpath->query('/atom:entry'); + + $conversation = ""; + $conversationlist = array(); + $item_id = 0; + + // Reverse the order of the entries + $entrylist = array(); + + foreach ($entries AS $entry) + $entrylist[] = $entry; + + foreach (array_reverse($entrylist) AS $entry) { + + $mention = false; + + // fetch the author + if ($first_child == "feed") + $author = self::fetchauthor($xpath, $doc->firstChild, $importer, $contact, false); + else + $author = self::fetchauthor($xpath, $entry, $importer, $contact, false); + + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $nickname = $value; + else + $nickname = $author["author-name"]; + + $item = array_merge($header, $author); + + // Now get the item + $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["uri"])); + if ($r) { + logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); + continue; + } + + $item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue)); + $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; + + if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) { + $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + $item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue; + } elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION) + $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + + $item["object"] = $xml; + $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; + + /// @TODO + /// Delete a message + if ($item["verb"] == "qvitter-delete-notice") { + // ignore "Delete" messages (by now) + logger("Ignore delete message ".print_r($item, true)); + continue; + } + + if ($item["verb"] == ACTIVITY_JOIN) { + // ignore "Join" messages + logger("Ignore join message ".print_r($item, true)); + continue; + } + + if ($item["verb"] == ACTIVITY_FOLLOW) { + new_follower($importer, $contact, $item, $nickname); + continue; + } + + if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") { + lose_follower($importer, $contact, $item, $dummy); + continue; + } + + if ($item["verb"] == ACTIVITY_FAVORITE) { + $orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue; + logger("Favorite ".$orig_uri." ".print_r($item, true)); + + $item["verb"] = ACTIVITY_LIKE; + $item["parent-uri"] = $orig_uri; + $item["gravity"] = GRAVITY_LIKE; + } + + if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") { + // Ignore "Unfavorite" message + logger("Ignore unfavorite message ".print_r($item, true)); + continue; + } + + // http://activitystrea.ms/schema/1.0/rsvp-yes + if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE))) + logger("Unhandled verb ".$item["verb"]." ".print_r($item, true)); + + $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; + $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; + $conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue; + + $related = ""; + + $inreplyto = $xpath->query('thr:in-reply-to', $entry); + if (is_object($inreplyto->item(0))) { + foreach($inreplyto->item(0)->attributes AS $attributes) { + if ($attributes->name == "ref") + $item["parent-uri"] = $attributes->textContent; + if ($attributes->name == "href") + $related = $attributes->textContent; + } + } + + $georsspoint = $xpath->query('georss:point', $entry); + if ($georsspoint) + $item["coord"] = $georsspoint->item(0)->nodeValue; + + $categories = $xpath->query('atom:category', $entry); + if ($categories) { + foreach ($categories AS $category) { + foreach($category->attributes AS $attributes) + if ($attributes->name == "term") { + $term = $attributes->textContent; + if(strlen($item["tag"])) + $item["tag"] .= ','; + $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + } + } + } + + $self = ""; + $enclosure = ""; + + $links = $xpath->query('atom:link', $entry); + if ($links) { + $rel = ""; + $href = ""; + $type = ""; + $length = "0"; + $title = ""; + foreach ($links AS $link) { + foreach($link->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "rel") + $rel = $attributes->textContent; + if ($attributes->name == "type") + $type = $attributes->textContent; + if ($attributes->name == "length") + $length = $attributes->textContent; + if ($attributes->name == "title") + $title = $attributes->textContent; + } + if (($rel != "") AND ($href != "")) + switch($rel) { + case "alternate": + $item["plink"] = $href; + if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR + ($item["object-type"] == ACTIVITY_OBJ_EVENT)) + $item["body"] .= add_page_info($href); + break; + case "ostatus:conversation": + $conversation = $href; + break; + case "enclosure": + $enclosure = $href; + if(strlen($item["attach"])) + $item["attach"] .= ','; + + $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; + break; + case "related": + if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) { + if (!isset($item["parent-uri"])) + $item["parent-uri"] = $href; + + if ($related == "") + $related = $href; + } else + $item["body"] .= add_page_info($href); + break; + case "self": + $self = $href; + break; + case "mentioned": + // Notification check + if ($importer["nurl"] == normalise_link($href)) + $mention = true; + break; + } + } + } + + $local_id = ""; + $repeat_of = ""; + + $notice_info = $xpath->query('statusnet:notice_info', $entry); + if ($notice_info AND ($notice_info->length > 0)) { + foreach($notice_info->item(0)->attributes AS $attributes) { + if ($attributes->name == "source") + $item["app"] = strip_tags($attributes->textContent); + if ($attributes->name == "local_id") + $local_id = $attributes->textContent; + if ($attributes->name == "repeat_of") + $repeat_of = $attributes->textContent; + } + } + + // Is it a repeated post? + if ($repeat_of != "") { + $activityobjects = $xpath->query('activity:object', $entry)->item(0); + + if (is_object($activityobjects)) { + + $orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue; + if (!isset($orig_uri)) + $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue; + + $orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects); + if ($orig_links AND ($orig_links->length > 0)) + foreach($orig_links->item(0)->attributes AS $attributes) + if ($attributes->name == "href") + $orig_link = $attributes->textContent; + + if (!isset($orig_link)) + $orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue; + + if (!isset($orig_link)) + $orig_link = self::convert_href($orig_uri); + + $orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue; + if (!isset($orig_body)) + $orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue; + + $orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue; + + $orig_contact = $contact; + $orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false); + + $item["author-name"] = $orig_author["author-name"]; + $item["author-link"] = $orig_author["author-link"]; + $item["author-avatar"] = $orig_author["author-avatar"]; + $item["body"] = add_page_info_to_body(html2bbcode($orig_body)); + $item["created"] = $orig_created; + + $item["uri"] = $orig_uri; + $item["plink"] = $orig_link; + + $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue; + + $item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue; + if (!isset($item["object-type"])) + $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue; + } + } + + //if ($enclosure != "") + // $item["body"] .= add_page_info($enclosure); + + if (isset($item["parent-uri"])) { + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["parent-uri"])); + + if (!$r AND ($related != "")) { + $reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom"; + + if ($reply_path != $related) { + logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG); + $reply_xml = fetch_url($reply_path); + + $reply_contact = $contact; + self::import($reply_xml,$importer,$reply_contact, $reply_hub); + + // After the import try to fetch the parent item again + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["parent-uri"])); + } + } + if ($r) { + $item["type"] = 'remote-comment'; + $item["gravity"] = GRAVITY_COMMENT; + } + } else + $item["parent-uri"] = $item["uri"]; + + $item_id = self::completion($conversation, $importer["uid"], $item, $self); + + if (!$item_id) { + logger("Error storing item", LOGGER_DEBUG); + continue; + } + + logger("Item was stored with id ".$item_id, LOGGER_DEBUG); + } + } + + public static function convert_href($href) { + $elements = explode(":",$href); + + if ((count($elements) <= 2) OR ($elements[0] != "tag")) + return $href; + + $server = explode(",", $elements[1]); + $conversation = explode("=", $elements[2]); + + if ((count($elements) == 4) AND ($elements[2] == "post")) + return "http://".$server[0]."/notice/".$elements[3]; + + if ((count($conversation) != 2) OR ($conversation[1] =="")) + return $href; + + if ($elements[3] == "objectType=thread") + return "http://".$server[0]."/conversation/".$conversation[1]; + else + return "http://".$server[0]."/notice/".$conversation[1]; + + return $href; + } + + public static function check_conversations($mentions = false, $override = false) { + $last = get_config('system','ostatus_last_poll'); + + $poll_interval = intval(get_config('system','ostatus_poll_interval')); + if(! $poll_interval) + $poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL; + + // Don't poll if the interval is set negative + if (($poll_interval < 0) AND !$override) + return; + + if (!$mentions) { + $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); + if (!$poll_timeframe) + $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME; + } else { + $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); + if (!$poll_timeframe) + $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS; + } + + + if ($last AND !$override) { + $next = $last + ($poll_interval * 60); + if ($next > time()) { + logger('poll interval not reached'); + return; + } + } + + logger('cron_start'); + + $start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60)); + + if ($mentions) + $conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term` + STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid` + WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention` + GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start)); + else + $conversations = q("SELECT `oid`, `url`, `uid` FROM `term` + WHERE `type` = 7 AND `term` > '%s' + GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start)); + + foreach ($conversations AS $conversation) { + self::completion($conversation['url'], $conversation['uid']); + } + + logger('cron_end'); + + set_config('system','ostatus_last_poll', time()); + } + + /** + * @brief Updates the gcontact table with actor data from the conversation + * + * @param object $actor The actor object that contains the contact data + */ + private function conv_fetch_actor($actor) { + + // We set the generation to "3" since the data here is not as reliable as the data we get on other occasions + $contact = array("network" => NETWORK_OSTATUS, "generation" => 3); + + if (isset($actor->url)) + $contact["url"] = $actor->url; + + if (isset($actor->displayName)) + $contact["name"] = $actor->displayName; + + if (isset($actor->portablecontacts_net->displayName)) + $contact["name"] = $actor->portablecontacts_net->displayName; + + if (isset($actor->portablecontacts_net->preferredUsername)) + $contact["nick"] = $actor->portablecontacts_net->preferredUsername; + + if (isset($actor->id)) + $contact["alias"] = $actor->id; + + if (isset($actor->summary)) + $contact["about"] = $actor->summary; + + if (isset($actor->portablecontacts_net->note)) + $contact["about"] = $actor->portablecontacts_net->note; + + if (isset($actor->portablecontacts_net->addresses->formatted)) + $contact["location"] = $actor->portablecontacts_net->addresses->formatted; + + + if (isset($actor->image->url)) + $contact["photo"] = $actor->image->url; + + if (isset($actor->image->width)) + $avatarwidth = $actor->image->width; + + if (is_array($actor->status_net->avatarLinks)) + foreach ($actor->status_net->avatarLinks AS $avatar) { + if ($avatarsize < $avatar->width) { + $contact["photo"] = $avatar->url; + $avatarsize = $avatar->width; + } + } + + update_gcontact($contact); + } + + /** + * @brief Fetches the conversation url for a given item link or conversation id + * + * @param string $self The link to the posting + * @param string $conversation_id The conversation id + * + * @return string The conversation url + */ + private function fetch_conversation($self, $conversation_id = "") { + + if ($conversation_id != "") { + $elements = explode(":", $conversation_id); + + if ((count($elements) <= 2) OR ($elements[0] != "tag")) + return $conversation_id; + } + + if ($self == "") + return ""; + + $json = str_replace(".atom", ".json", $self); + + $raw = fetch_url($json); + if ($raw == "") + return ""; + + $data = json_decode($raw); + if (!is_object($data)) + return ""; + + $conversation_id = $data->statusnet_conversation_id; + + $pos = strpos($self, "/api/statuses/show/"); + $base_url = substr($self, 0, $pos); + + return $base_url."/conversation/".$conversation_id; + } + + /** + * @brief Fetches actor details of a given actor and user id + * + * @param string $actor The actor url + * @param int $uid The user id + * @param int $contact_id The default contact-id + * + * @return array Array with actor details + */ + private function get_actor_details($actor, $uid, $contact_id) { + + $details = array(); + + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + $uid, normalise_link($actor), NETWORK_STATUSNET); + + if (!$contact) + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'", + $uid, $actor, normalise_link($actor), NETWORK_STATUSNET); + + if ($contact) { + logger("Found contact for url ".$actor, LOGGER_DEBUG); + $details["contact_id"] = $contact[0]["id"]; + $details["network"] = $contact[0]["network"]; + + $details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); + } else { + logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG); + + // Adding a global contact + /// @TODO Use this data for the post + $details["global_contact_id"] = get_contact($actor, 0); + + logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); + + $details["contact_id"] = $contact_id; + $details["network"] = NETWORK_OSTATUS; + + $details["not_following"] = true; + } + + return $details; + } + + private function completion($conversation_url, $uid, $item = array(), $self = "") { + + + $item_stored = -1; + + $conversation_url = self::fetch_conversation($self, $conversation_url); + + // If the thread shouldn't be completed then store the item and go away + // Don't do a completion on liked content + if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR + ($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) { + $item_stored = item_store($item, true); + return($item_stored); + } + + // Get the parent + $parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN + (SELECT `parent` FROM `item` WHERE `id` IN + (SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))", + intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url)); + + if ($parents) + $parent = $parents[0]; + elseif (count($item) > 0) { + $parent = $item; + $parent["type"] = "remote"; + $parent["verb"] = ACTIVITY_POST; + $parent["visible"] = 1; + } else { + // Preset the parent + $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid); + if (!$r) + return(-2); + + $parent = array(); + $parent["id"] = 0; + $parent["parent"] = 0; + $parent["uri"] = ""; + $parent["contact-id"] = $r[0]["id"]; + $parent["type"] = "remote"; + $parent["verb"] = ACTIVITY_POST; + $parent["visible"] = 1; + } + + $conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as"; + $pageno = 1; + $items = array(); + + logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid); + + do { + $conv_arr = z_fetch_url($conv."?page=".$pageno); + + // If it is a non-ssl site and there is an error, then try ssl or vice versa + if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) { + $conv = str_replace("http://", "https://", $conv); + $conv_as = fetch_url($conv."?page=".$pageno); + } elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) { + $conv = str_replace("https://", "http://", $conv); + $conv_as = fetch_url($conv."?page=".$pageno); + } else + $conv_as = $conv_arr["body"]; + + $conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as); + $conv_as = json_decode($conv_as); + + $no_of_items = sizeof($items); + + if (@is_array($conv_as->items)) + foreach ($conv_as->items AS $single_item) + $items[$single_item->id] = $single_item; + + if ($no_of_items == sizeof($items)) + break; + + $pageno++; + + } while (true); + + logger('fetching conversation done. Found '.count($items).' items'); + + if (!sizeof($items)) { + if (count($item) > 0) { + $item_stored = item_store($item, true); + + if ($item_stored) { + logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG); + self::store_conversation($item_id, $conversation_url); + } + + return($item_stored); + } else + return(-3); + } + + $items = array_reverse($items); + + $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid)); + $importer = $r[0]; + + $new_parent = true; + + foreach ($items as $single_conv) { + + // Update the gcontact table + self::conv_fetch_actor($single_conv->actor); + + // Test - remove before flight + //$tempfile = tempnam(get_temppath(), "conversation"); + //file_put_contents($tempfile, json_encode($single_conv)); + + $mention = false; + + if (isset($single_conv->object->id)) + $single_conv->id = $single_conv->object->id; + + $plink = self::convert_href($single_conv->id); + if (isset($single_conv->object->url)) + $plink = self::convert_href($single_conv->object->url); + + if (@!$single_conv->id) + continue; + + logger("Got id ".$single_conv->id, LOGGER_DEBUG); + + if ($first_id == "") { + $first_id = $single_conv->id; + + // The first post of the conversation isn't our first post. There are three options: + // 1. Our conversation hasn't the "real" thread starter + // 2. This first post is a post inside our thread + // 3. This first post is a post inside another thread + if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { + + $new_parent = true; + + $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN + (SELECT `parent` FROM `item` + WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1", + intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($new_parents) { + if ($new_parents[0]["parent"] == $parent["parent"]) { + // Option 2: This post is already present inside our thread - but not as thread starter + logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG); + $first_id = $parent["uri"]; + } else { + // Option 3: Not so good. We have mixed parents. We have to see how to clean this up. + // For now just take the new parent. + $parent = $new_parents[0]; + $first_id = $parent["uri"]; + logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG); + } + } else { + // Option 1: We hadn't got the real thread starter + // We have to clean up our existing messages. + $parent["id"] = 0; + $parent["uri"] = $first_id; + logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG); + } + } elseif ($parent["uri"] == "") { + $parent["id"] = 0; + $parent["uri"] = $first_id; + } + } + + $parent_uri = $parent["uri"]; + + // "context" only seems to exist on older servers + if (isset($single_conv->context->inReplyTo->id)) { + $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", + intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($parent_exists) + $parent_uri = $single_conv->context->inReplyTo->id; + } + + // This is the current way + if (isset($single_conv->object->inReplyTo->id)) { + $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", + intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($parent_exists) + $parent_uri = $single_conv->object->inReplyTo->id; + } + + $message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", + intval($uid), dbesc($single_conv->id), + dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($message_exists) { + logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG); + + if ($parent["id"] != 0) { + $existing_message = $message_exists[0]; + + // We improved the way we fetch OStatus messages, this shouldn't happen very often now + /// @TODO We have to change the shadow copies as well. This way here is really ugly. + if ($existing_message["parent"] != $parent["id"]) { + logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG); + + // Update the parent id of the selected item + $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d", + intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"])); + + // Update the parent uri in the thread - but only if it points to itself + $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`", + dbesc($parent_uri), intval($existing_message["id"])); + + // try to change all items of the same parent + $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d", + intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"])); + + // Update the parent uri in the thread - but only if it points to itself + $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)", + dbesc($parent["uri"]), intval($existing_message["parent"])); + + // Now delete the thread + delete_thread($existing_message["parent"]); + } + } + + // The item we are having on the system is the one that we wanted to store via the item array + if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) { + $item = array(); + $item_stored = 0; + } + + continue; + } + + if (is_array($single_conv->to)) + foreach($single_conv->to AS $to) + if ($importer["nurl"] == normalise_link($to->id)) + $mention = true; + + $actor = $single_conv->actor->id; + if (isset($single_conv->actor->url)) + $actor = $single_conv->actor->url; + + $details = self::get_actor_details($actor, $uid, $parent["contact-id"]); + + // Do we only want to import threads that were started by our contacts? + if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) { + logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG); + continue; + } + + $arr = array(); + $arr["network"] = $details["network"]; + $arr["uri"] = $single_conv->id; + $arr["plink"] = $plink; + $arr["uid"] = $uid; + $arr["contact-id"] = $details["contact_id"]; + $arr["parent-uri"] = $parent_uri; + $arr["created"] = $single_conv->published; + $arr["edited"] = $single_conv->published; + $arr["owner-name"] = $single_conv->actor->displayName; + if ($arr["owner-name"] == '') + $arr["owner-name"] = $single_conv->actor->contact->displayName; + if ($arr["owner-name"] == '') + $arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName; + + $arr["owner-link"] = $actor; + $arr["owner-avatar"] = $single_conv->actor->image->url; + $arr["author-name"] = $arr["owner-name"]; + $arr["author-link"] = $actor; + $arr["author-avatar"] = $single_conv->actor->image->url; + $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content)); + + if (isset($single_conv->status_net->notice_info->source)) + $arr["app"] = strip_tags($single_conv->status_net->notice_info->source); + elseif (isset($single_conv->statusnet->notice_info->source)) + $arr["app"] = strip_tags($single_conv->statusnet->notice_info->source); + elseif (isset($single_conv->statusnet_notice_info->source)) + $arr["app"] = strip_tags($single_conv->statusnet_notice_info->source); + elseif (isset($single_conv->provider->displayName)) + $arr["app"] = $single_conv->provider->displayName; + else + $arr["app"] = "OStatus"; + + + $arr["object"] = json_encode($single_conv); + $arr["verb"] = $parent["verb"]; + $arr["visible"] = $parent["visible"]; + $arr["location"] = $single_conv->location->displayName; + $arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon); + + // Is it a reshared item? + if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) { + if (is_array($single_conv->object)) + $single_conv->object = $single_conv->object[0]; + + logger("Found reshared item ".$single_conv->object->id); + + // $single_conv->object->context->conversation; + + if (isset($single_conv->object->object->id)) + $arr["uri"] = $single_conv->object->object->id; + else + $arr["uri"] = $single_conv->object->id; + + if (isset($single_conv->object->object->url)) + $plink = self::convert_href($single_conv->object->object->url); + else + $plink = self::convert_href($single_conv->object->url); + + if (isset($single_conv->object->object->content)) + $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content)); + else + $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content)); + + $arr["plink"] = $plink; + + $arr["created"] = $single_conv->object->published; + $arr["edited"] = $single_conv->object->published; + + $arr["author-name"] = $single_conv->object->actor->displayName; + if ($arr["owner-name"] == '') + $arr["author-name"] = $single_conv->object->actor->contact->displayName; + + $arr["author-link"] = $single_conv->object->actor->url; + $arr["author-avatar"] = $single_conv->object->actor->image->url; + + $arr["app"] = $single_conv->object->provider->displayName."#"; + //$arr["verb"] = $single_conv->object->verb; + + $arr["location"] = $single_conv->object->location->displayName; + $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon); + } + + if ($arr["location"] == "") + unset($arr["location"]); + + if ($arr["coord"] == "") + unset($arr["coord"]); + + // Copy fields from given item array + if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] == $single_conv->id))) { + $copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar", + "gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag", + "title", "attach", "app", "type", "location", "contact-id", "uri"); + foreach ($copy_fields AS $field) + if (isset($item[$field])) + $arr[$field] = $item[$field]; + + } + + $newitem = item_store($arr); + if (!$newitem) { + logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG); + continue; + } + + if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) { + $item = array(); + $item_stored = $newitem; + } + + logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG); + + // Add the conversation entry (but don't fetch the whole conversation) + self::store_conversation($newitem, $conversation_url); + + // If the newly created item is the top item then change the parent settings of the thread + // This shouldn't happen anymore. This is supposed to be absolote. + if ($arr["uri"] == $first_id) { + logger('setting new parent to id '.$newitem); + $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", + intval($uid), intval($newitem)); + if ($new_parents) + $parent = $new_parents[0]; + } + } + + if (($item_stored < 0) AND (count($item) > 0)) { + + if (get_config('system','ostatus_full_threads')) { + $details = self::get_actor_details($item["owner-link"], $uid, $item["contact-id"]); + if ($details["not_following"]) { + logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG); + return false; + } + } + + $item_stored = item_store($item, true); + if ($item_stored) { + logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG); + self::store_conversation($item_stored, $conversation_url); + } + } + + return($item_stored); + } + + private function store_conversation($itemid, $conversation_url) { + + $conversation_url = self::convert_href($conversation_url); + + $messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid)); + if (!$messages) + return; + $message = $messages[0]; + + // Store conversation url if not done before + $conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d", + intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION)); + + if (!$conversation) { + $r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", + intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), + dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"])); + logger('Storing conversation url '.$conversation_url.' for id '.$itemid); + } + } + + private function get_reshared_guid($item) { + $body = trim($item["body"]); + + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(""); + + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(""); + + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + // Skip if there is no shared message in there + if ($body == $attributes) + return(false); + + $guid = ""; + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + preg_match('/guid="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + return $guid; + } + + private function format_picture_post($body) { + $siteinfo = get_attached_data($body); + + if (($siteinfo["type"] == "photo")) { + if (isset($siteinfo["preview"])) + $preview = $siteinfo["preview"]; + else + $preview = $siteinfo["image"]; + + // Is it a remote picture? Then make a smaller preview here + $preview = proxy_url($preview, false, PROXY_SIZE_SMALL); + + // Is it a local picture? Then make it smaller here + $preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview); + $preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview); + + if (isset($siteinfo["url"])) + $url = $siteinfo["url"]; + else + $url = $siteinfo["image"]; + + $body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]"; + } + + return $body; + } + + private function add_header($doc, $owner) { + + $a = get_app(); + + $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); + $doc->appendChild($root); + + $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $root->setAttribute("xmlns:poco", NAMESPACE_POCO); + $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); + + $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); + xml2::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); + xml2::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]); + xml2::add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"])); + xml2::add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"])); + xml2::add_element($doc, $root, "logo", $owner["photo"]); + xml2::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); + + $author = self::add_author($doc, $owner); + $root->appendChild($author); + + $attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html"); + xml2::add_element($doc, $root, "link", "", $attributes); + + /// @TODO We have to find out what this is + /// $attributes = array("href" => App::get_baseurl()."/sup", + /// "rel" => "http://api.friendfeed.com/2008/03#sup", + /// "type" => "application/json"); + /// xml2::add_element($doc, $root, "link", "", $attributes); + + self::hublinks($doc, $root); + + $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon"); + xml2::add_element($doc, $root, "link", "", $attributes); + + $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies"); + xml2::add_element($doc, $root, "link", "", $attributes); + + $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention"); + xml2::add_element($doc, $root, "link", "", $attributes); + + $attributes = array("href" => App::get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom", + "rel" => "self", "type" => "application/atom+xml"); + xml2::add_element($doc, $root, "link", "", $attributes); + + return $root; + } + + public static function hublinks($doc, $root) { + $hub = get_config('system','huburl'); + + $hubxml = ''; + if(strlen($hub)) { + $hubs = explode(',', $hub); + if(count($hubs)) { + foreach($hubs as $h) { + $h = trim($h); + if(! strlen($h)) + continue; + if ($h === '[internal]') + $h = App::get_baseurl() . '/pubsubhubbub'; + xml2::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub")); + } + } + } + } + + private function get_attachment($doc, $root, $item) { + $o = ""; + $siteinfo = get_attached_data($item["body"]); + + switch($siteinfo["type"]) { + case 'link': + $attributes = array("rel" => "enclosure", + "href" => $siteinfo["url"], + "type" => "text/html; charset=UTF-8", + "length" => "", + "title" => $siteinfo["title"]); + xml2::add_element($doc, $root, "link", "", $attributes); + break; + case 'photo': + $imgdata = get_photo_info($siteinfo["image"]); + $attributes = array("rel" => "enclosure", + "href" => $siteinfo["image"], + "type" => $imgdata["mime"], + "length" => intval($imgdata["size"])); + xml2::add_element($doc, $root, "link", "", $attributes); + break; + case 'video': + $attributes = array("rel" => "enclosure", + "href" => $siteinfo["url"], + "type" => "text/html; charset=UTF-8", + "length" => "", + "title" => $siteinfo["title"]); + xml2::add_element($doc, $root, "link", "", $attributes); + break; + default: + break; + } + + if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) { + $photodata = get_photo_info($siteinfo["image"]); + + $attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]); + xml2::add_element($doc, $root, "link", "", $attributes); + } + + + $arr = explode('[/attach],',$item['attach']); + if(count($arr)) { + foreach($arr as $r) { + $matches = false; + $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches); + if($cnt) { + $attributes = array("rel" => "enclosure", + "href" => $matches[1], + "type" => $matches[3]); + + if(intval($matches[2])) + $attributes["length"] = intval($matches[2]); + + if(trim($matches[4]) != "") + $attributes["title"] = trim($matches[4]); + + xml2::add_element($doc, $root, "link", "", $attributes); + } + } + } + } + + private function add_author($doc, $owner) { + + $r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"])); + if ($r) + $profile = $r[0]; + + $author = $doc->createElement("author"); + xml2::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON); + xml2::add_element($doc, $author, "uri", $owner["url"]); + xml2::add_element($doc, $author, "name", $owner["name"]); + xml2::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7)); + + $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]); + xml2::add_element($doc, $author, "link", "", $attributes); + + $attributes = array( + "rel" => "avatar", + "type" => "image/jpeg", // To-Do? + "media:width" => 175, + "media:height" => 175, + "href" => $owner["photo"]); + xml2::add_element($doc, $author, "link", "", $attributes); + + if (isset($owner["thumb"])) { + $attributes = array( + "rel" => "avatar", + "type" => "image/jpeg", // To-Do? + "media:width" => 80, + "media:height" => 80, + "href" => $owner["thumb"]); + xml2::add_element($doc, $author, "link", "", $attributes); + } + + xml2::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]); + xml2::add_element($doc, $author, "poco:displayName", $owner["name"]); + xml2::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7)); + + if (trim($owner["location"]) != "") { + $element = $doc->createElement("poco:address"); + xml2::add_element($doc, $element, "poco:formatted", $owner["location"]); + $author->appendChild($element); + } + + if (trim($profile["homepage"]) != "") { + $urls = $doc->createElement("poco:urls"); + xml2::add_element($doc, $urls, "poco:type", "homepage"); + xml2::add_element($doc, $urls, "poco:value", $profile["homepage"]); + xml2::add_element($doc, $urls, "poco:primary", "true"); + $author->appendChild($urls); + } + + if (count($profile)) { + xml2::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"])); + xml2::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"])); + } + + return $author; + } + + /** + * @TODO Picture attachments should look like this: + * <a href="https://status.pirati.ca/attachment/572819" title="https://status.pirati.ca/file/heluecht-20151202T222602-rd3u49p.gif" + * class="attachment thumbnail" id="attachment-572819" rel="nofollow external">https://status.pirati.ca/attachment/572819</a> + * + */ + + function construct_verb($item) { + if ($item['verb']) + return $item['verb']; + return ACTIVITY_POST; + } + + function construct_objecttype($item) { + if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT))) + return $item['object-type']; + return ACTIVITY_OBJ_NOTE; + } + + private function entry($doc, $item, $owner, $toplevel = false) { + $repeated_guid = self::get_reshared_guid($item); + if ($repeated_guid != "") + $xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel); + + if ($xml) + return $xml; + + if ($item["verb"] == ACTIVITY_LIKE) + return self::like_entry($doc, $item, $owner, $toplevel); + else + return self::note_entry($doc, $item, $owner, $toplevel); + } + + private function source_entry($doc, $contact) { + $source = $doc->createElement("source"); + xml2::add_element($doc, $source, "id", $contact["poll"]); + xml2::add_element($doc, $source, "title", $contact["name"]); + xml2::add_element($doc, $source, "link", "", array("rel" => "alternate", + "type" => "text/html", + "href" => $contact["alias"])); + xml2::add_element($doc, $source, "link", "", array("rel" => "self", + "type" => "application/atom+xml", + "href" => $contact["poll"])); + xml2::add_element($doc, $source, "icon", $contact["photo"]); + xml2::add_element($doc, $source, "updated", datetime_convert("UTC","UTC",$contact["success_update"]."+00:00",ATOM_TIME)); + + return $source; + } + + private function contact_entry($url, $owner) { + + $r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1", + dbesc(normalise_link($url)), intval($owner["uid"])); + if ($r) { + $contact = $r[0]; + $contact["uid"] = -1; + } + + if (!$r) { + $r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1", + dbesc(normalise_link($url))); + if ($r) { + $contact = $r[0]; + $contact["uid"] = -1; + $contact["success_update"] = $contact["updated"]; + } + } + + if (!$r) + $contact = owner; + + if (!isset($contact["poll"])) { + $data = probe_url($url); + $contact["alias"] = $data["alias"]; + $contact["poll"] = $data["poll"]; + } + + if (!isset($contact["alias"])) + $contact["alias"] = $contact["url"]; + + return $contact; + } + + private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) { + + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + + $title = self::entry_header($doc, $entry, $owner, $toplevel); + + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' AND NOT `private` AND `network` IN ('%s', '%s', '%s') LIMIT 1", + intval($owner["uid"]), dbesc($repeated_guid), + dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); + if ($r) + $repeated_item = $r[0]; + else + return false; + + $contact = self::contact_entry($repeated_item['author-link'], $owner); + + $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); + + $title = $owner["nick"]." repeated a notice by ".$contact["nick"]; + + self::entry_content($doc, $entry, $item, $owner, $title, ACTIVITY_SHARE, false); + + $as_object = $doc->createElement("activity:object"); + +// ostatusWaEeYs +// ostatusogu9zg - besser + xml2::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity"); + + self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false); + + $author = self::add_author($doc, $contact); + $as_object->appendChild($author); + + $as_object2 = $doc->createElement("activity:object"); + + xml2::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item)); + + $title = sprintf("New comment by %s", $contact["nick"]); + + self::entry_content($doc, $as_object2, $repeated_item, $owner, $title); + + $as_object->appendChild($as_object2); + + self::entry_footer($doc, $as_object, $item, $owner, false); + + $source = self::source_entry($doc, $contact); + + $as_object->appendChild($source); + + $entry->appendChild($as_object); + + self::entry_footer($doc, $entry, $item, $owner); + + return $entry; + } + + private function like_entry($doc, $item, $owner, $toplevel) { + + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + + $title = self::entry_header($doc, $entry, $owner, $toplevel); + + $verb = NAMESPACE_ACTIVITY_SCHEMA."favorite"; + self::entry_content($doc, $entry, $item, $owner, "Favorite", $verb, false); + + $as_object = $doc->createElement("activity:object"); + + $parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"])); + $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); + + xml2::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0])); + + self::entry_content($doc, $as_object, $parent[0], $owner, "New entry"); + + $entry->appendChild($as_object); + + self::entry_footer($doc, $entry, $item, $owner); + + return $entry; + } + + private function note_entry($doc, $item, $owner, $toplevel) { + + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + + $title = self::entry_header($doc, $entry, $owner, $toplevel); + + xml2::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); + + self::entry_content($doc, $entry, $item, $owner, $title); + + self::entry_footer($doc, $entry, $item, $owner); + + return $entry; + } + + private function entry_header($doc, &$entry, $owner, $toplevel) { + if (!$toplevel) { + $entry = $doc->createElement("entry"); + $title = sprintf("New note by %s", $owner["nick"]); + } else { + $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry"); + + $entry->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $entry->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $entry->setAttribute("xmlns:poco", NAMESPACE_POCO); + $entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); + + $author = self::add_author($doc, $owner); + $entry->appendChild($author); + + $title = sprintf("New comment by %s", $owner["nick"]); + } + return $title; + } + + private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) { + + if ($verb == "") + $verb = self::construct_verb($item); + + xml2::add_element($doc, $entry, "id", $item["uri"]); + xml2::add_element($doc, $entry, "title", $title); + + $body = self::format_picture_post($item['body']); + + if ($item['title'] != "") + $body = "[b]".$item['title']."[/b]\n\n".$body; + + $body = bbcode($body, false, false, 7); + + xml2::add_element($doc, $entry, "content", $body, array("type" => "html")); + + xml2::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", + "href" => App::get_baseurl()."/display/".$item["guid"])); + + if ($complete) + xml2::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"])); + + xml2::add_element($doc, $entry, "activity:verb", $verb); + + xml2::add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); + xml2::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); + } + + private function entry_footer($doc, $entry, $item, $owner, $complete = true) { + + $mentioned = array(); + + if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { + $parent = q("SELECT `guid`, `author-link`, `owner-link` 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"]); + xml2::add_element($doc, $entry, "thr:in-reply-to", "", $attributes); + + $attributes = array( + "rel" => "related", + "href" => App::get_baseurl()."/display/".$parent[0]["guid"]); + xml2::add_element($doc, $entry, "link", "", $attributes); + + $mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"]; + $mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"]; + + $thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($owner["uid"]), + dbesc($parent_item)); + if ($thrparent) { + $mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"]; + $mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"]; + } + } + + xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation", + "href" => App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"])); + xml2::add_element($doc, $entry, "ostatus:conversation", App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]); + + $tags = item_getfeedtags($item); + + if(count($tags)) + foreach($tags as $t) + if ($t[0] == "@") + $mentioned[$t[1]] = $t[1]; + + // Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS) + $newmentions = array(); + foreach ($mentioned AS $mention) { + $newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention); + $newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention); + } + $mentioned = $newmentions; + + foreach ($mentioned AS $mention) { + $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", + intval($owner["uid"]), + dbesc(normalise_link($mention))); + if ($r[0]["forum"] OR $r[0]["prv"]) + xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_GROUP, + "href" => $mention)); + else + xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_PERSON, + "href" => $mention)); + } + + if (!$item["private"]) { + xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention", + "href" => "http://activityschema.org/collection/public")); + xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection", + "href" => "http://activityschema.org/collection/public")); + } + + if(count($tags)) + foreach($tags as $t) + if ($t[0] != "@") + xml2::add_element($doc, $entry, "category", "", array("term" => $t[2])); + + self::get_attachment($doc, $entry, $item); + + if ($complete) { + $app = $item["app"]; + if ($app == "") + $app = "web"; + + $attributes = array("local_id" => $item["id"], "source" => $app); + + if (isset($parent["id"])) + $attributes["repeat_of"] = $parent["id"]; + + if ($item["coord"] != "") + xml2::add_element($doc, $entry, "georss:point", $item["coord"]); + + xml2::add_element($doc, $entry, "statusnet:notice_info", "", $attributes); + } + } + + public static function feed(&$a, $owner_nick, $last_update) { + + $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` + FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` + WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1", + dbesc($owner_nick)); + if (!$r) + return; + + $owner = $r[0]; + + if(!strlen($last_update)) + $last_update = 'now -30 days'; + + $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); + + $items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item` + INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` + LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid` + WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted` + AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' + AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`)) + OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`) + AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`)) + OR (`item`.`author-link` IN ('%s', '%s'))) + ORDER BY `item`.`received` DESC + LIMIT 0, 300", + intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN), + //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), + //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), + dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), + dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), + dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])), + dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])) + ); + + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + + $root = self::add_header($doc, $owner); + + foreach ($items AS $item) { + $entry = self::entry($doc, $item, $owner); + $root->appendChild($entry); + } + + return(trim($doc->saveXML())); + } + + public static function salmon($item,$owner) { + + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + + $entry = self::entry($doc, $item, $owner, true); + + $doc->appendChild($entry); + + return(trim($doc->saveXML())); + } +} +?> diff --git a/include/pubsubpublish.php b/include/pubsubpublish.php index 4fbb505146..625eefc261 100644 --- a/include/pubsubpublish.php +++ b/include/pubsubpublish.php @@ -16,7 +16,7 @@ function handle_pubsubhubbub() { logger("Generate feed for user ".$rr['nickname']." - last updated ".$rr['last_update'], LOGGER_DEBUG); - $params = ostatus_feed($a, $rr['nickname'], $rr['last_update']); + $params = ostatus::feed($a, $rr['nickname'], $rr['last_update']); $hmac_sig = hash_hmac("sha1", $params, $rr['secret']); $headers = array("Content-type: application/atom+xml", diff --git a/mod/salmon.php b/mod/salmon.php index 9c22e42d11..37230a5573 100644 --- a/mod/salmon.php +++ b/mod/salmon.php @@ -84,7 +84,7 @@ function salmon_post(&$a) { // decode the data $data = base64url_decode($data); - $author = ostatus_salmon_author($data,$importer); + $author = ostatus::salmon_author($data,$importer); $author_link = $author["author-link"]; if(! $author_link) { @@ -181,7 +181,7 @@ function salmon_post(&$a) { $contact_rec = ((count($r)) ? $r[0] : null); - ostatus_import($data,$importer,$contact_rec, $hub); + ostatus::import($data,$importer,$contact_rec, $hub); http_status_exit(200); } From 4a5a964d8959883b73c541af1fe6474fd835e1b9 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 30 Mar 2016 12:46:10 +0200 Subject: [PATCH 240/273] "Scrape" now respects the new url formats with "index.php" --- include/Scrape.php | 49 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/include/Scrape.php b/include/Scrape.php index 03d21047e7..deff0b080f 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -356,7 +356,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { $result = array(); - if(! $url) + if (!$url) return $result; $result = Cache::get("probe_url:".$mode.":".$url); @@ -365,6 +365,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { return $result; } + $original_url = $url; $network = null; $diaspora = false; $diaspora_base = ''; @@ -393,7 +394,12 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { else $links = lrdd($url); - if(count($links)) { + if ((count($links) == 0) AND strstr($url, "/index.php")) { + $url = str_replace("/index.php", "", $url); + $links = lrdd($url); + } + + if (count($links)) { $has_lrdd = true; logger('probe_url: found lrdd links: ' . print_r($links,true), LOGGER_DATA); @@ -440,12 +446,21 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { // aliases, let's hope we're lucky and get one that matches the feed author-uri because // otherwise we're screwed. + $backup_alias = ""; + foreach($links as $link) { if($link['@attributes']['rel'] === 'alias') { if(strpos($link['@attributes']['href'],'@') === false) { if(isset($profile)) { - if(($link['@attributes']['href'] !== $profile) AND ($alias == "")) - $alias = unamp($link['@attributes']['href']); + $alias_url = $link['@attributes']['href']; + + if(($alias_url !== $profile) AND ($backup_alias == "") AND + ($alias_url !== str_replace("/index.php", "", $profile))) + $backup_alias = $alias_url; + + if(($alias_url !== $profile) AND !strstr($alias_url, "index.php") AND + ($alias_url !== str_replace("/index.php", "", $profile))) + $alias = $alias_url; } else $profile = unamp($link['@attributes']['href']); @@ -453,6 +468,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { } } + if ($alias == "") + $alias = $backup_alias; + // If the profile is different from the url then the url is abviously an alias if (($alias == "") AND ($profile != "") AND !$at_addr AND (normalise_link($profile) != normalise_link($url))) $alias = $url; @@ -769,6 +787,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { if (($baseurl == "") AND ($poll != "")) $baseurl = matching_url(normalise_link($profile), normalise_link($poll)); + if (substr($baseurl, -10) == "/index.php") + $baseurl = str_replace("/index.php", "", $baseurl); + $baseurl = rtrim($baseurl, "/"); if(strpos($url,'@') AND ($addr == "") AND ($network == NETWORK_DFRN)) @@ -816,8 +837,24 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { } // Only store into the cache if the value seems to be valid - if ($result['network'] != NETWORK_PHANTOM) - Cache::set("probe_url:".$mode.":".$url,serialize($result), CACHE_DAY); + if ($result['network'] != NETWORK_PHANTOM) { + Cache::set("probe_url:".$mode.":".$original_url,serialize($result), CACHE_DAY); + + /// @todo temporary fix - we need a real contact update function that updates only changing fields + /// The biggest problem is the avatar picture that could have a reduced image size. + /// It should only be updated if the existing picture isn't existing anymore. + if (($result['network'] != NETWORK_FEED) AND $result["addr"] AND $result["name"] AND $result["nick"]) + q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s', + `name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self`", + dbesc($result["addr"]), + dbesc($result["alias"]), + dbesc($result["name"]), + dbesc($result["nick"]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(normalise_link($result['url'])) + ); + } return $result; } From 2c5b5c1cd471c890b7e06e28dc979c2a0e8736b0 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 30 Mar 2016 23:25:20 +0200 Subject: [PATCH 241/273] OStatus class is now moved into the right place --- include/cron.php | 4 +- include/dfrn.php | 191 +-- include/ostatus.php | 3081 ++++++++++++++++++++++--------------------- include/xml.php | 35 + 4 files changed, 1688 insertions(+), 1623 deletions(-) diff --git a/include/cron.php b/include/cron.php index 60c62786e6..c60284b738 100644 --- a/include/cron.php +++ b/include/cron.php @@ -101,10 +101,10 @@ function cron_run(&$argv, &$argc){ // Check OStatus conversations // Check only conversations with mentions (for a longer time) - check_conversations(true); + ostatus::check_conversations(true); // Check every conversation - check_conversations(false); + ostatus::check_conversations(false); // Set the gcontact-id in the item table if missing item_set_gcontact(); diff --git a/include/dfrn.php b/include/dfrn.php index d96805a56b..14be747305 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -19,6 +19,7 @@ require_once("include/text.php"); require_once("include/oembed.php"); require_once("include/html2bbcode.php"); require_once("include/bbcode.php"); +require_once("include/xml.php"); /** * @brief This class contain functions to create and send DFRN XML files @@ -286,17 +287,17 @@ class dfrn { $mail = $doc->createElement("dfrn:mail"); $sender = $doc->createElement("dfrn:sender"); - xml_add_element($doc, $sender, "dfrn:name", $owner['name']); - xml_add_element($doc, $sender, "dfrn:uri", $owner['url']); - xml_add_element($doc, $sender, "dfrn:avatar", $owner['thumb']); + xml::add_element($doc, $sender, "dfrn:name", $owner['name']); + xml::add_element($doc, $sender, "dfrn:uri", $owner['url']); + xml::add_element($doc, $sender, "dfrn:avatar", $owner['thumb']); $mail->appendChild($sender); - xml_add_element($doc, $mail, "dfrn:id", $item['uri']); - xml_add_element($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']); - xml_add_element($doc, $mail, "dfrn:sentdate", datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME)); - xml_add_element($doc, $mail, "dfrn:subject", $item['title']); - xml_add_element($doc, $mail, "dfrn:content", $item['body']); + xml::add_element($doc, $mail, "dfrn:id", $item['uri']); + xml::add_element($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']); + xml::add_element($doc, $mail, "dfrn:sentdate", datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME)); + xml::add_element($doc, $mail, "dfrn:subject", $item['title']); + xml::add_element($doc, $mail, "dfrn:content", $item['body']); $root->appendChild($mail); @@ -319,11 +320,11 @@ class dfrn { $suggest = $doc->createElement("dfrn:suggest"); - xml_add_element($doc, $suggest, "dfrn:url", $item['url']); - xml_add_element($doc, $suggest, "dfrn:name", $item['name']); - xml_add_element($doc, $suggest, "dfrn:photo", $item['photo']); - xml_add_element($doc, $suggest, "dfrn:request", $item['request']); - xml_add_element($doc, $suggest, "dfrn:note", $item['note']); + xml::add_element($doc, $suggest, "dfrn:url", $item['url']); + xml::add_element($doc, $suggest, "dfrn:name", $item['name']); + xml::add_element($doc, $suggest, "dfrn:photo", $item['photo']); + xml::add_element($doc, $suggest, "dfrn:request", $item['request']); + xml::add_element($doc, $suggest, "dfrn:note", $item['note']); $root->appendChild($suggest); @@ -365,16 +366,16 @@ class dfrn { $relocate = $doc->createElement("dfrn:relocate"); - xml_add_element($doc, $relocate, "dfrn:url", $owner['url']); - xml_add_element($doc, $relocate, "dfrn:name", $owner['name']); - xml_add_element($doc, $relocate, "dfrn:photo", $photos[4]); - xml_add_element($doc, $relocate, "dfrn:thumb", $photos[5]); - xml_add_element($doc, $relocate, "dfrn:micro", $photos[6]); - xml_add_element($doc, $relocate, "dfrn:request", $owner['request']); - xml_add_element($doc, $relocate, "dfrn:confirm", $owner['confirm']); - xml_add_element($doc, $relocate, "dfrn:notify", $owner['notify']); - xml_add_element($doc, $relocate, "dfrn:poll", $owner['poll']); - xml_add_element($doc, $relocate, "dfrn:sitepubkey", get_config('system','site_pubkey')); + xml::add_element($doc, $relocate, "dfrn:url", $owner['url']); + xml::add_element($doc, $relocate, "dfrn:name", $owner['name']); + xml::add_element($doc, $relocate, "dfrn:photo", $photos[4]); + xml::add_element($doc, $relocate, "dfrn:thumb", $photos[5]); + xml::add_element($doc, $relocate, "dfrn:micro", $photos[6]); + xml::add_element($doc, $relocate, "dfrn:request", $owner['request']); + xml::add_element($doc, $relocate, "dfrn:confirm", $owner['confirm']); + xml::add_element($doc, $relocate, "dfrn:notify", $owner['notify']); + xml::add_element($doc, $relocate, "dfrn:poll", $owner['poll']); + xml::add_element($doc, $relocate, "dfrn:sitepubkey", get_config('system','site_pubkey')); $root->appendChild($relocate); @@ -410,39 +411,39 @@ class dfrn { $root->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, "title", $owner["name"]); + 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); - xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); + xml::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); $attributes = array("rel" => "license", "href" => "http://creativecommons.org/licenses/by/3.0/"); - xml_add_element($doc, $root, "link", "", $attributes); + xml::add_element($doc, $root, "link", "", $attributes); $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink); - xml_add_element($doc, $root, "link", "", $attributes); + xml::add_element($doc, $root, "link", "", $attributes); if ($public) { // DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed. - ostatus_hublinks($doc, $root); + ostatus::hublinks($doc, $root); $attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); - xml_add_element($doc, $root, "link", "", $attributes); + xml::add_element($doc, $root, "link", "", $attributes); $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); - xml_add_element($doc, $root, "link", "", $attributes); + xml::add_element($doc, $root, "link", "", $attributes); $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); - xml_add_element($doc, $root, "link", "", $attributes); + xml::add_element($doc, $root, "link", "", $attributes); } if ($owner['page-flags'] == PAGE_COMMUNITY) - xml_add_element($doc, $root, "dfrn:community", 1); + xml::add_element($doc, $root, "dfrn:community", 1); /// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP" - xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); + xml::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); $author = self::add_author($doc, $owner, $authorelement, $public); $root->appendChild($author); @@ -468,26 +469,26 @@ class dfrn { $picdate = datetime_convert('UTC', 'UTC', $owner['avatar-date'].'+00:00', ATOM_TIME); $attributes = array("dfrn:updated" => $namdate); - xml_add_element($doc, $author, "name", $owner["name"], $attributes); + xml::add_element($doc, $author, "name", $owner["name"], $attributes); $attributes = array("dfrn:updated" => $namdate); - 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); $attributes = array("dfrn:updated" => $namdate); - xml_add_element($doc, $author, "dfrn:handle", $owner["addr"], $attributes); + xml::add_element($doc, $author, "dfrn:handle", $owner["addr"], $attributes); $attributes = array("rel" => "photo", "type" => "image/jpeg", "dfrn:updated" => $picdate, "media:width" => 175, "media:height" => 175, "href" => $owner['photo']); - xml_add_element($doc, $author, "link", "", $attributes); + xml::add_element($doc, $author, "link", "", $attributes); $attributes = array("rel" => "avatar", "type" => "image/jpeg", "dfrn:updated" => $picdate, "media:width" => 175, "media:height" => 175, "href" => $owner['photo']); - xml_add_element($doc, $author, "link", "", $attributes); + xml::add_element($doc, $author, "link", "", $attributes); $birthday = feed_birthday($owner['uid'], $owner['timezone']); if ($birthday) - xml_add_element($doc, $author, "dfrn:birthday", $birthday); + xml::add_element($doc, $author, "dfrn:birthday", $birthday); // The following fields will only be generated if this isn't for a public feed if ($public) @@ -502,25 +503,25 @@ class dfrn { intval($owner['uid'])); if ($r) { $profile = $r[0]; - xml_add_element($doc, $author, "poco:displayName", $profile["name"]); - xml_add_element($doc, $author, "poco:updated", $namdate); + xml::add_element($doc, $author, "poco:displayName", $profile["name"]); + xml::add_element($doc, $author, "poco:updated", $namdate); if (trim($profile["dob"]) != "0000-00-00") - xml_add_element($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"]))); + xml::add_element($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"]))); - xml_add_element($doc, $author, "poco:note", $profile["about"]); - xml_add_element($doc, $author, "poco:preferredUsername", $profile["nickname"]); + xml::add_element($doc, $author, "poco:note", $profile["about"]); + xml::add_element($doc, $author, "poco:preferredUsername", $profile["nickname"]); $savetz = date_default_timezone_get(); date_default_timezone_set($profile["timezone"]); - xml_add_element($doc, $author, "poco:utcOffset", date("P")); + xml::add_element($doc, $author, "poco:utcOffset", date("P")); date_default_timezone_set($savetz); if (trim($profile["homepage"]) != "") { $urls = $doc->createElement("poco:urls"); - xml_add_element($doc, $urls, "poco:type", "homepage"); - xml_add_element($doc, $urls, "poco:value", $profile["homepage"]); - xml_add_element($doc, $urls, "poco:primary", "true"); + xml::add_element($doc, $urls, "poco:type", "homepage"); + xml::add_element($doc, $urls, "poco:value", $profile["homepage"]); + xml::add_element($doc, $urls, "poco:primary", "true"); $author->appendChild($urls); } @@ -528,7 +529,7 @@ class dfrn { $keywords = explode(",", $profile["pub_keywords"]); foreach ($keywords AS $keyword) - xml_add_element($doc, $author, "poco:tags", trim($keyword)); + xml::add_element($doc, $author, "poco:tags", trim($keyword)); } @@ -536,25 +537,25 @@ class dfrn { $xmpp = ""; if (trim($xmpp) != "") { $ims = $doc->createElement("poco:ims"); - xml_add_element($doc, $ims, "poco:type", "xmpp"); - xml_add_element($doc, $ims, "poco:value", $xmpp); - xml_add_element($doc, $ims, "poco:primary", "true"); + xml::add_element($doc, $ims, "poco:type", "xmpp"); + xml::add_element($doc, $ims, "poco:value", $xmpp); + xml::add_element($doc, $ims, "poco:primary", "true"); $author->appendChild($ims); } if (trim($profile["locality"].$profile["region"].$profile["country-name"]) != "") { $element = $doc->createElement("poco:address"); - xml_add_element($doc, $element, "poco:formatted", formatted_location($profile)); + xml::add_element($doc, $element, "poco:formatted", formatted_location($profile)); if (trim($profile["locality"]) != "") - xml_add_element($doc, $element, "poco:locality", $profile["locality"]); + xml::add_element($doc, $element, "poco:locality", $profile["locality"]); if (trim($profile["region"]) != "") - xml_add_element($doc, $element, "poco:region", $profile["region"]); + xml::add_element($doc, $element, "poco:region", $profile["region"]); if (trim($profile["country-name"]) != "") - xml_add_element($doc, $element, "poco:country", $profile["country-name"]); + xml::add_element($doc, $element, "poco:country", $profile["country-name"]); $author->appendChild($element); } @@ -578,9 +579,9 @@ class dfrn { $contact = get_contact_details_by_url($contact_url, $item["uid"]); $author = $doc->createElement($element); - xml_add_element($doc, $author, "name", $contact["name"]); - xml_add_element($doc, $author, "uri", $contact["url"]); - xml_add_element($doc, $author, "dfrn:handle", $contact["addr"]); + xml::add_element($doc, $author, "name", $contact["name"]); + xml::add_element($doc, $author, "uri", $contact["url"]); + xml::add_element($doc, $author, "dfrn:handle", $contact["addr"]); /// @Todo /// - Check real image type and image size @@ -591,7 +592,7 @@ class dfrn { "media:width" => 80, "media:height" => 80, "href" => $contact["photo"]); - xml_add_element($doc, $author, "link", "", $attributes); + xml::add_element($doc, $author, "link", "", $attributes); $attributes = array( "rel" => "avatar", @@ -599,7 +600,7 @@ class dfrn { "media:width" => 80, "media:height" => 80, "href" => $contact["photo"]); - xml_add_element($doc, $author, "link", "", $attributes); + xml::add_element($doc, $author, "link", "", $attributes); return $author; } @@ -622,11 +623,11 @@ class dfrn { if(!$r) return false; if($r->type) - xml_add_element($doc, $entry, "activity:object-type", $r->type); + xml::add_element($doc, $entry, "activity:object-type", $r->type); if($r->id) - xml_add_element($doc, $entry, "id", $r->id); + xml::add_element($doc, $entry, "id", $r->id); if($r->title) - xml_add_element($doc, $entry, "title", $r->title); + xml::add_element($doc, $entry, "title", $r->title); if($r->link) { if(substr($r->link,0,1) == '<') { if(strstr($r->link,'&') && (! strstr($r->link,'&'))) @@ -641,16 +642,16 @@ class dfrn { $attributes = array(); foreach ($link->attributes() AS $parameter => $value) $attributes[$parameter] = $value; - xml_add_element($doc, $entry, "link", "", $attributes); + xml::add_element($doc, $entry, "link", "", $attributes); } } } else { $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link); - xml_add_element($doc, $entry, "link", "", $attributes); + xml::add_element($doc, $entry, "link", "", $attributes); } } if($r->content) - xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html")); + xml::add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html")); return $entry; } @@ -684,7 +685,7 @@ class dfrn { if(trim($matches[4]) != "") $attributes["title"] = trim($matches[4]); - xml_add_element($doc, $root, "link", "", $attributes); + xml::add_element($doc, $root, "link", "", $attributes); } } } @@ -711,7 +712,7 @@ class dfrn { if($item['deleted']) { $attributes = array("ref" => $item['uri'], "when" => datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)); - return xml_create_element($doc, "at:deleted-entry", "", $attributes); + return xml::create_element($doc, "at:deleted-entry", "", $attributes); } $entry = $doc->createElement("entry"); @@ -745,66 +746,66 @@ class dfrn { $attributes = array("ref" => $parent_item, "type" => "text/html", "href" => app::get_baseurl().'/display/'.$parent[0]['guid'], "dfrn:diaspora_guid" => $parent[0]['guid']); - xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes); + xml::add_element($doc, $entry, "thr:in-reply-to", "", $attributes); } - xml_add_element($doc, $entry, "id", $item["uri"]); - xml_add_element($doc, $entry, "title", $item["title"]); + xml::add_element($doc, $entry, "id", $item["uri"]); + xml::add_element($doc, $entry, "title", $item["title"]); - xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); - xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); + xml::add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); + xml::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); // "dfrn:env" is used to read the content - xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true)); + xml::add_element($doc, $entry, "dfrn:env", base64url_encode($body, true)); // The "content" field is not read by the receiver. We could remove it when the type is "text" // We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env" - xml_add_element($doc, $entry, "content", (($type == 'html') ? $htmlbody : $body), array("type" => $type)); + xml::add_element($doc, $entry, "content", (($type == 'html') ? $htmlbody : $body), array("type" => $type)); // 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", + xml::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", "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 if ($comment) - xml_add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child'])); + xml::add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child'])); if($item['location']) - xml_add_element($doc, $entry, "dfrn:location", $item['location']); + xml::add_element($doc, $entry, "dfrn:location", $item['location']); if($item['coord']) - xml_add_element($doc, $entry, "georss:point", $item['coord']); + xml::add_element($doc, $entry, "georss:point", $item['coord']); if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) - xml_add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1)); + xml::add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1)); if($item['extid']) - xml_add_element($doc, $entry, "dfrn:extid", $item['extid']); + xml::add_element($doc, $entry, "dfrn:extid", $item['extid']); if($item['bookmark']) - xml_add_element($doc, $entry, "dfrn:bookmark", "true"); + xml::add_element($doc, $entry, "dfrn:bookmark", "true"); if($item['app']) - xml_add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app'])); + xml::add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app'])); - xml_add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]); + xml::add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]); // The signed text contains the content in Markdown, the sender handle and the signatur for the content // It is needed for relayed comments to Diaspora. if($item['signed_text']) { $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer']))); - xml_add_element($doc, $entry, "dfrn:diaspora_signature", $sign); + xml::add_element($doc, $entry, "dfrn:diaspora_signature", $sign); } - xml_add_element($doc, $entry, "activity:verb", construct_verb($item)); + xml::add_element($doc, $entry, "activity:verb", construct_verb($item)); if ($item['object-type'] != "") - xml_add_element($doc, $entry, "activity:object-type", $item['object-type']); + xml::add_element($doc, $entry, "activity:object-type", $item['object-type']); elseif ($item['id'] == $item['parent']) - xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); + xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); else - xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT); + xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT); $actobj = self::create_activity($doc, "activity:object", $item['object']); if ($actobj) @@ -819,7 +820,7 @@ class dfrn { if(count($tags)) { foreach($tags as $t) if (($type != 'html') OR ($t[0] != "@")) - xml_add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2])); + xml::add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2])); } if(count($tags)) @@ -832,11 +833,11 @@ class dfrn { intval($owner["uid"]), dbesc(normalise_link($mention))); if ($r[0]["forum"] OR $r[0]["prv"]) - xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", + xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", "ostatus:object-type" => ACTIVITY_OBJ_GROUP, "href" => $mention)); else - xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", + xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", "ostatus:object-type" => ACTIVITY_OBJ_PERSON, "href" => $mention)); } @@ -1323,7 +1324,7 @@ class dfrn { $obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element); $activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue; - xml_add_element($obj_doc, $obj_element, "type", $activity_type); + xml::add_element($obj_doc, $obj_element, "type", $activity_type); $id = $xpath->query("atom:id", $activity)->item(0); if (is_object($id)) diff --git a/include/ostatus.php b/include/ostatus.php index 02447e3ade..ba5b80cd5d 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -12,485 +12,429 @@ require_once("include/Scrape.php"); require_once("include/follow.php"); require_once("include/api.php"); require_once("mod/proxy.php"); -require_once("include/ostatus2.php"); +require_once("include/xml.php"); -define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes -define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes -define('OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS', 14400); // given in minutes +class ostatus { + const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes + const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes + const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes -function ostatus_check_follow_friends() { - $r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''"); + private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) { - if (!$r) - return; + $author = array(); + $author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; + $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue; - foreach ($r AS $contact) { - ostatus_follow_friends($contact["uid"], $contact["v"]); - set_pconfig($contact["uid"], "system", "ostatus_legacy_contact", ""); - } -} + // Preserve the value + $authorlink = $author["author-link"]; -// This function doesn't work reliable by now. -function ostatus_follow_friends($uid, $url) { - $contact = probe_url($url); + $alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes; + if (is_object($alternate)) + foreach($alternate AS $attributes) + if ($attributes->name == "href") + $author["author-link"] = $attributes->textContent; - if (!$contact) - return; + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", + intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), + dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET)); + if ($r) { + $contact = $r[0]; + $author["contact-id"] = $r[0]["id"]; + } else + $author["contact-id"] = $contact["id"]; - $api = $contact["baseurl"]."/api/"; - - // Fetching friends - $data = z_fetch_url($api."statuses/friends.json?screen_name=".$contact["nick"]); - - if (!$data["success"]) - return; - - $friends = json_decode($data["body"]); - - foreach ($friends AS $friend) { - $url = $friend->statusnet_profile_url; - $r = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND - (`nurl` = '%s' OR `alias` = '%s' OR `alias` = '%s') AND - `network` != '%s' LIMIT 1", - intval($uid), dbesc(normalise_link($url)), - dbesc(normalise_link($url)), dbesc($url), dbesc(NETWORK_STATUSNET)); - if (!$r) { - $data = probe_url($friend->statusnet_profile_url); - if ($data["network"] == NETWORK_OSTATUS) { - $result = new_contact($uid,$friend->statusnet_profile_url); - if ($result["success"]) - logger($friend->name." ".$url." - success", LOGGER_DEBUG); - else - logger($friend->name." ".$url." - failed", LOGGER_DEBUG); - } else - logger($friend->name." ".$url." - not OStatus", LOGGER_DEBUG); + $avatarlist = array(); + $avatars = $xpath->query("atom:author/atom:link[@rel='avatar']", $context); + foreach($avatars AS $avatar) { + $href = ""; + $width = 0; + foreach($avatar->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "width") + $width = $attributes->textContent; + } + if (($width > 0) AND ($href != "")) + $avatarlist[$width] = $href; } - } -} - -function ostatus_fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) { - - $author = array(); - $author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; - $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue; - - // Preserve the value - $authorlink = $author["author-link"]; - - $alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes; - if (is_object($alternate)) - foreach($alternate AS $attributes) - if ($attributes->name == "href") - $author["author-link"] = $attributes->textContent; - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", - intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), - dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET)); - if ($r) { - $contact = $r[0]; - $author["contact-id"] = $r[0]["id"]; - } else - $author["contact-id"] = $contact["id"]; - - $avatarlist = array(); - $avatars = $xpath->query("atom:author/atom:link[@rel='avatar']", $context); - foreach($avatars AS $avatar) { - $href = ""; - $width = 0; - foreach($avatar->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "width") - $width = $attributes->textContent; - } - if (($width > 0) AND ($href != "")) - $avatarlist[$width] = $href; - } - if (count($avatarlist) > 0) { - krsort($avatarlist); - $author["author-avatar"] = current($avatarlist); - } - - $displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($displayname != "") - $author["author-name"] = $displayname; - - $author["owner-name"] = $author["author-name"]; - $author["owner-link"] = $author["author-link"]; - $author["owner-avatar"] = $author["author-avatar"]; - - // Only update the contacts if it is an OStatus contact - if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { - // Update contact data - - $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; - if ($value != "") - $contact["notify"] = $value; - - $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["alias"] = $value; - - $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["name"] = $value; - - $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["nick"] = $value; - - $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["about"] = html2bbcode($value); - - $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["location"] = $value; - - if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { - - logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - - q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", - dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), - dbesc(datetime_convert()), intval($contact["id"])); - - poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], - "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); + if (count($avatarlist) > 0) { + krsort($avatarlist); + $author["author-avatar"] = current($avatarlist); } - if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) { - logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); + $displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($displayname != "") + $author["author-name"] = $displayname; - update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); - } + $author["owner-name"] = $author["author-name"]; + $author["owner-link"] = $author["author-link"]; + $author["owner-avatar"] = $author["author-avatar"]; - $contact["generation"] = 2; - $contact["photo"] = $author["author-avatar"]; - update_gcontact($contact); - } + // Only update the contacts if it is an OStatus contact + if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { + // Update contact data - return($author); -} + $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; + if ($value != "") + $contact["notify"] = $value; -function ostatus_salmon_author($xml, $importer) { - $a = get_app(); + $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["alias"] = $value; - if ($xml == "") - return; + $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; - $doc = new DOMDocument(); - @$doc->loadXML($xml); + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; - $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', NAMESPACE_ATOM1); - $xpath->registerNamespace('thr', NAMESPACE_THREAD); - $xpath->registerNamespace('georss', NAMESPACE_GEORSS); - $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); - $xpath->registerNamespace('media', NAMESPACE_MEDIA); - $xpath->registerNamespace('poco', NAMESPACE_POCO); - $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); - $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); + $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["about"] = html2bbcode($value); - $entries = $xpath->query('/atom:entry'); + $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; - foreach ($entries AS $entry) { - // fetch the author - $author = ostatus_fetchauthor($xpath, $entry, $importer, $contact, true); - return $author; - } -} + if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { -function ostatus_import($xml,$importer,&$contact, &$hub) { + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - $a = get_app(); + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), + dbesc(datetime_convert()), intval($contact["id"])); - logger("Import OStatus message", LOGGER_DEBUG); - - if ($xml == "") - return; - - $doc = new DOMDocument(); - @$doc->loadXML($xml); - - $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', NAMESPACE_ATOM1); - $xpath->registerNamespace('thr', NAMESPACE_THREAD); - $xpath->registerNamespace('georss', NAMESPACE_GEORSS); - $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); - $xpath->registerNamespace('media', NAMESPACE_MEDIA); - $xpath->registerNamespace('poco', NAMESPACE_POCO); - $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); - $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); - - $gub = ""; - $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes; - if (is_object($hub_attributes)) - foreach($hub_attributes AS $hub_attribute) - if ($hub_attribute->name == "href") { - $hub = $hub_attribute->textContent; - logger("Found hub ".$hub, LOGGER_DEBUG); + poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], + "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); } - $header = array(); - $header["uid"] = $importer["uid"]; - $header["network"] = NETWORK_OSTATUS; - $header["type"] = "remote"; - $header["wall"] = 0; - $header["origin"] = 0; - $header["gravity"] = GRAVITY_PARENT; + if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) { + logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); - // it could either be a received post or a post we fetched by ourselves - // depending on that, the first node is different - $first_child = $doc->firstChild->tagName; + update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); + } + + $contact["generation"] = 2; + $contact["photo"] = $author["author-avatar"]; + update_gcontact($contact); + } + + return($author); + } + + public static function salmon_author($xml, $importer) { + + if ($xml == "") + return; + + $doc = new DOMDocument(); + @$doc->loadXML($xml); + + $xpath = new DomXPath($doc); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); - if ($first_child == "feed") - $entries = $xpath->query('/atom:feed/atom:entry'); - else $entries = $xpath->query('/atom:entry'); - $conversation = ""; - $conversationlist = array(); - $item_id = 0; - - // Reverse the order of the entries - $entrylist = array(); - - foreach ($entries AS $entry) - $entrylist[] = $entry; - - foreach (array_reverse($entrylist) AS $entry) { - - $mention = false; - - // fetch the author - if ($first_child == "feed") - $author = ostatus_fetchauthor($xpath, $doc->firstChild, $importer, $contact, false); - else - $author = ostatus_fetchauthor($xpath, $entry, $importer, $contact, false); - - $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; - if ($value != "") - $nickname = $value; - else - $nickname = $author["author-name"]; - - $item = array_merge($header, $author); - - // Now get the item - $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["uri"])); - if ($r) { - logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); - continue; + foreach ($entries AS $entry) { + // fetch the author + $author = self::fetchauthor($xpath, $entry, $importer, $contact, true); + return $author; } + } - $item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue)); - $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; + public static function import($xml,$importer,&$contact, &$hub) { - if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) { - $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; - $item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue; - } elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION) - $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + logger("Import OStatus message", LOGGER_DEBUG); - $item["object"] = $xml; - $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; + if ($xml == "") + return; - /// @TODO - /// Delete a message - if ($item["verb"] == "qvitter-delete-notice") { - // ignore "Delete" messages (by now) - logger("Ignore delete message ".print_r($item, true)); - continue; - } + //$tempfile = tempnam(get_temppath(), "import"); + //file_put_contents($tempfile, $xml); - if ($item["verb"] == ACTIVITY_JOIN) { - // ignore "Join" messages - logger("Ignore join message ".print_r($item, true)); - continue; - } + $doc = new DOMDocument(); + @$doc->loadXML($xml); - if ($item["verb"] == ACTIVITY_FOLLOW) { - new_follower($importer, $contact, $item, $nickname); - continue; - } + $xpath = new DomXPath($doc); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); - if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") { - lose_follower($importer, $contact, $item, $dummy); - continue; - } - - if ($item["verb"] == ACTIVITY_FAVORITE) { - $orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue; - logger("Favorite ".$orig_uri." ".print_r($item, true)); - - $item["verb"] = ACTIVITY_LIKE; - $item["parent-uri"] = $orig_uri; - $item["gravity"] = GRAVITY_LIKE; - } - - if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") { - // Ignore "Unfavorite" message - logger("Ignore unfavorite message ".print_r($item, true)); - continue; - } - - // http://activitystrea.ms/schema/1.0/rsvp-yes - if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE))) - logger("Unhandled verb ".$item["verb"]." ".print_r($item, true)); - - $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; - $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; - $conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue; - - $related = ""; - - $inreplyto = $xpath->query('thr:in-reply-to', $entry); - if (is_object($inreplyto->item(0))) { - foreach($inreplyto->item(0)->attributes AS $attributes) { - if ($attributes->name == "ref") - $item["parent-uri"] = $attributes->textContent; - if ($attributes->name == "href") - $related = $attributes->textContent; - } - } - - $georsspoint = $xpath->query('georss:point', $entry); - if ($georsspoint) - $item["coord"] = $georsspoint->item(0)->nodeValue; - - /// @TODO - /// $item["location"] = - - $categories = $xpath->query('atom:category', $entry); - if ($categories) { - foreach ($categories AS $category) { - foreach($category->attributes AS $attributes) - if ($attributes->name == "term") { - $term = $attributes->textContent; - if(strlen($item["tag"])) - $item["tag"] .= ','; - $item["tag"] .= "#[url=".$a->get_baseurl()."/search?tag=".$term."]".$term."[/url]"; - } - } - } - - $self = ""; - $enclosure = ""; - - $links = $xpath->query('atom:link', $entry); - if ($links) { - $rel = ""; - $href = ""; - $type = ""; - $length = "0"; - $title = ""; - foreach ($links AS $link) { - foreach($link->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "rel") - $rel = $attributes->textContent; - if ($attributes->name == "type") - $type = $attributes->textContent; - if ($attributes->name == "length") - $length = $attributes->textContent; - if ($attributes->name == "title") - $title = $attributes->textContent; + $gub = ""; + $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes; + if (is_object($hub_attributes)) + foreach($hub_attributes AS $hub_attribute) + if ($hub_attribute->name == "href") { + $hub = $hub_attribute->textContent; + logger("Found hub ".$hub, LOGGER_DEBUG); } - if (($rel != "") AND ($href != "")) - switch($rel) { - case "alternate": - $item["plink"] = $href; - if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR - ($item["object-type"] == ACTIVITY_OBJ_EVENT)) - $item["body"] .= add_page_info($href); - break; - case "ostatus:conversation": - $conversation = $href; - break; - case "enclosure": - $enclosure = $href; - if(strlen($item["attach"])) - $item["attach"] .= ','; - $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; - break; - case "related": - if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) { - if (!isset($item["parent-uri"])) - $item["parent-uri"] = $href; + $header = array(); + $header["uid"] = $importer["uid"]; + $header["network"] = NETWORK_OSTATUS; + $header["type"] = "remote"; + $header["wall"] = 0; + $header["origin"] = 0; + $header["gravity"] = GRAVITY_PARENT; - if ($related == "") - $related = $href; - } else - $item["body"] .= add_page_info($href); - break; - case "self": - $self = $href; - break; - case "mentioned": - // Notification check - if ($importer["nurl"] == normalise_link($href)) - $mention = true; - break; - } + // it could either be a received post or a post we fetched by ourselves + // depending on that, the first node is different + $first_child = $doc->firstChild->tagName; + + if ($first_child == "feed") + $entries = $xpath->query('/atom:feed/atom:entry'); + else + $entries = $xpath->query('/atom:entry'); + + $conversation = ""; + $conversationlist = array(); + $item_id = 0; + + // Reverse the order of the entries + $entrylist = array(); + + foreach ($entries AS $entry) + $entrylist[] = $entry; + + foreach (array_reverse($entrylist) AS $entry) { + + $mention = false; + + // fetch the author + if ($first_child == "feed") + $author = self::fetchauthor($xpath, $doc->firstChild, $importer, $contact, false); + else + $author = self::fetchauthor($xpath, $entry, $importer, $contact, false); + + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $nickname = $value; + else + $nickname = $author["author-name"]; + + $item = array_merge($header, $author); + + // Now get the item + $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["uri"])); + if ($r) { + logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); + continue; } - } - $local_id = ""; - $repeat_of = ""; + $item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue)); + $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; - $notice_info = $xpath->query('statusnet:notice_info', $entry); - if ($notice_info AND ($notice_info->length > 0)) { - foreach($notice_info->item(0)->attributes AS $attributes) { - if ($attributes->name == "source") - $item["app"] = strip_tags($attributes->textContent); - if ($attributes->name == "local_id") - $local_id = $attributes->textContent; - if ($attributes->name == "repeat_of") - $repeat_of = $attributes->textContent; + if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) { + $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + $item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue; + } elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION) + $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; + + $item["object"] = $xml; + $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; + + /// @TODO + /// Delete a message + if ($item["verb"] == "qvitter-delete-notice") { + // ignore "Delete" messages (by now) + logger("Ignore delete message ".print_r($item, true)); + continue; } - } - // Is it a repeated post? - if ($repeat_of != "") { - $activityobjects = $xpath->query('activity:object', $entry)->item(0); + if ($item["verb"] == ACTIVITY_JOIN) { + // ignore "Join" messages + logger("Ignore join message ".print_r($item, true)); + continue; + } - if (is_object($activityobjects)) { + if ($item["verb"] == ACTIVITY_FOLLOW) { + new_follower($importer, $contact, $item, $nickname); + continue; + } - $orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue; - if (!isset($orig_uri)) - $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue; + if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") { + lose_follower($importer, $contact, $item, $dummy); + continue; + } - $orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects); - if ($orig_links AND ($orig_links->length > 0)) - foreach($orig_links->item(0)->attributes AS $attributes) + if ($item["verb"] == ACTIVITY_FAVORITE) { + $orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue; + logger("Favorite ".$orig_uri." ".print_r($item, true)); + + $item["verb"] = ACTIVITY_LIKE; + $item["parent-uri"] = $orig_uri; + $item["gravity"] = GRAVITY_LIKE; + } + + if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") { + // Ignore "Unfavorite" message + logger("Ignore unfavorite message ".print_r($item, true)); + continue; + } + + // http://activitystrea.ms/schema/1.0/rsvp-yes + if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE))) + logger("Unhandled verb ".$item["verb"]." ".print_r($item, true)); + + $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; + $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; + $conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue; + + $related = ""; + + $inreplyto = $xpath->query('thr:in-reply-to', $entry); + if (is_object($inreplyto->item(0))) { + foreach($inreplyto->item(0)->attributes AS $attributes) { + if ($attributes->name == "ref") + $item["parent-uri"] = $attributes->textContent; + if ($attributes->name == "href") + $related = $attributes->textContent; + } + } + + $georsspoint = $xpath->query('georss:point', $entry); + if ($georsspoint) + $item["coord"] = $georsspoint->item(0)->nodeValue; + + $categories = $xpath->query('atom:category', $entry); + if ($categories) { + foreach ($categories AS $category) { + foreach($category->attributes AS $attributes) + if ($attributes->name == "term") { + $term = $attributes->textContent; + if(strlen($item["tag"])) + $item["tag"] .= ','; + $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + } + } + } + + $self = ""; + $enclosure = ""; + + $links = $xpath->query('atom:link', $entry); + if ($links) { + $rel = ""; + $href = ""; + $type = ""; + $length = "0"; + $title = ""; + foreach ($links AS $link) { + foreach($link->attributes AS $attributes) { if ($attributes->name == "href") - $orig_link = $attributes->textContent; + $href = $attributes->textContent; + if ($attributes->name == "rel") + $rel = $attributes->textContent; + if ($attributes->name == "type") + $type = $attributes->textContent; + if ($attributes->name == "length") + $length = $attributes->textContent; + if ($attributes->name == "title") + $title = $attributes->textContent; + } + if (($rel != "") AND ($href != "")) + switch($rel) { + case "alternate": + $item["plink"] = $href; + if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR + ($item["object-type"] == ACTIVITY_OBJ_EVENT)) + $item["body"] .= add_page_info($href); + break; + case "ostatus:conversation": + $conversation = $href; + break; + case "enclosure": + $enclosure = $href; + if(strlen($item["attach"])) + $item["attach"] .= ','; - if (!isset($orig_link)) - $orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue; + $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; + break; + case "related": + if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) { + if (!isset($item["parent-uri"])) + $item["parent-uri"] = $href; - if (!isset($orig_link)) - $orig_link = ostatus_convert_href($orig_uri); + if ($related == "") + $related = $href; + } else + $item["body"] .= add_page_info($href); + break; + case "self": + $self = $href; + break; + case "mentioned": + // Notification check + if ($importer["nurl"] == normalise_link($href)) + $mention = true; + break; + } + } + } - $orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue; - if (!isset($orig_body)) - $orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue; + $local_id = ""; + $repeat_of = ""; - $orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue; + $notice_info = $xpath->query('statusnet:notice_info', $entry); + if ($notice_info AND ($notice_info->length > 0)) { + foreach($notice_info->item(0)->attributes AS $attributes) { + if ($attributes->name == "source") + $item["app"] = strip_tags($attributes->textContent); + if ($attributes->name == "local_id") + $local_id = $attributes->textContent; + if ($attributes->name == "repeat_of") + $repeat_of = $attributes->textContent; + } + } - $orig_contact = $contact; - $orig_author = ostatus_fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false); + // Is it a repeated post? + if ($repeat_of != "") { + $activityobjects = $xpath->query('activity:object', $entry)->item(0); + + if (is_object($activityobjects)) { + + $orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue; + if (!isset($orig_uri)) + $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue; + + $orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects); + if ($orig_links AND ($orig_links->length > 0)) + foreach($orig_links->item(0)->attributes AS $attributes) + if ($attributes->name == "href") + $orig_link = $attributes->textContent; + + if (!isset($orig_link)) + $orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue; + + if (!isset($orig_link)) + $orig_link = self::convert_href($orig_uri); + + $orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue; + if (!isset($orig_body)) + $orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue; + + $orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue; + + $orig_contact = $contact; + $orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false); - //if (!intval(get_config('system','wall-to-wall_share'))) { - // $prefix = share_header($orig_author['author-name'], $orig_author['author-link'], $orig_author['author-avatar'], "", $orig_created, $orig_link); - // $item["body"] = $prefix.add_page_info_to_body(html2bbcode($orig_body))."[/share]"; - //} else { $item["author-name"] = $orig_author["author-name"]; $item["author-link"] = $orig_author["author-link"]; $item["author-avatar"] = $orig_author["author-avatar"]; @@ -499,1234 +443,1319 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { $item["uri"] = $orig_uri; $item["plink"] = $orig_link; - //} - $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue; + $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue; - $item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue; - if (!isset($item["object-type"])) - $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue; - } - } - - //if ($enclosure != "") - // $item["body"] .= add_page_info($enclosure); - - if (isset($item["parent-uri"])) { - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["parent-uri"])); - - if (!$r AND ($related != "")) { - $reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom"; - - if ($reply_path != $related) { - logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG); - $reply_xml = fetch_url($reply_path); - - $reply_contact = $contact; - ostatus_import($reply_xml,$importer,$reply_contact, $reply_hub); - - // After the import try to fetch the parent item again - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["parent-uri"])); + $item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue; + if (!isset($item["object-type"])) + $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue; } } - if ($r) { - $item["type"] = 'remote-comment'; - $item["gravity"] = GRAVITY_COMMENT; + + //if ($enclosure != "") + // $item["body"] .= add_page_info($enclosure); + + if (isset($item["parent-uri"])) { + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["parent-uri"])); + + if (!$r AND ($related != "")) { + $reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom"; + + if ($reply_path != $related) { + logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG); + $reply_xml = fetch_url($reply_path); + + $reply_contact = $contact; + self::import($reply_xml,$importer,$reply_contact, $reply_hub); + + // After the import try to fetch the parent item again + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($importer["uid"]), dbesc($item["parent-uri"])); + } + } + if ($r) { + $item["type"] = 'remote-comment'; + $item["gravity"] = GRAVITY_COMMENT; + } + } else + $item["parent-uri"] = $item["uri"]; + + $item_id = self::completion($conversation, $importer["uid"], $item, $self); + + if (!$item_id) { + logger("Error storing item", LOGGER_DEBUG); + continue; } - } else - $item["parent-uri"] = $item["uri"]; - $item_id = ostatus_completion($conversation, $importer["uid"], $item, $self); - - if (!$item_id) { - logger("Error storing item", LOGGER_DEBUG); - continue; - } - - logger("Item was stored with id ".$item_id, LOGGER_DEBUG); - } -} - -function ostatus_convert_href($href) { - $elements = explode(":",$href); - - if ((count($elements) <= 2) OR ($elements[0] != "tag")) - return $href; - - $server = explode(",", $elements[1]); - $conversation = explode("=", $elements[2]); - - if ((count($elements) == 4) AND ($elements[2] == "post")) - return "http://".$server[0]."/notice/".$elements[3]; - - if ((count($conversation) != 2) OR ($conversation[1] =="")) - return $href; - - if ($elements[3] == "objectType=thread") - return "http://".$server[0]."/conversation/".$conversation[1]; - else - return "http://".$server[0]."/notice/".$conversation[1]; - - return $href; -} - -function check_conversations($mentions = false, $override = false) { - $last = get_config('system','ostatus_last_poll'); - - $poll_interval = intval(get_config('system','ostatus_poll_interval')); - if(! $poll_interval) - $poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL; - - // Don't poll if the interval is set negative - if (($poll_interval < 0) AND !$override) - return; - - if (!$mentions) { - $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); - if (!$poll_timeframe) - $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME; - } else { - $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); - if (!$poll_timeframe) - $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS; - } - - - if ($last AND !$override) { - $next = $last + ($poll_interval * 60); - if ($next > time()) { - logger('poll interval not reached'); - return; + logger("Item was stored with id ".$item_id, LOGGER_DEBUG); } } - logger('cron_start'); - - $start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60)); - - if ($mentions) - $conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term` - STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid` - WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention` - GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start)); - else - $conversations = q("SELECT `oid`, `url`, `uid` FROM `term` - WHERE `type` = 7 AND `term` > '%s' - GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start)); - - foreach ($conversations AS $conversation) { - ostatus_completion($conversation['url'], $conversation['uid']); - } - - logger('cron_end'); - - set_config('system','ostatus_last_poll', time()); -} - -/** - * @brief Updates the gcontact table with actor data from the conversation - * - * @param object $actor The actor object that contains the contact data - */ -function ostatus_conv_fetch_actor($actor) { - - // We set the generation to "3" since the data here is not as reliable as the data we get on other occasions - $contact = array("network" => NETWORK_OSTATUS, "generation" => 3); - - if (isset($actor->url)) - $contact["url"] = $actor->url; - - if (isset($actor->displayName)) - $contact["name"] = $actor->displayName; - - if (isset($actor->portablecontacts_net->displayName)) - $contact["name"] = $actor->portablecontacts_net->displayName; - - if (isset($actor->portablecontacts_net->preferredUsername)) - $contact["nick"] = $actor->portablecontacts_net->preferredUsername; - - if (isset($actor->id)) - $contact["alias"] = $actor->id; - - if (isset($actor->summary)) - $contact["about"] = $actor->summary; - - if (isset($actor->portablecontacts_net->note)) - $contact["about"] = $actor->portablecontacts_net->note; - - if (isset($actor->portablecontacts_net->addresses->formatted)) - $contact["location"] = $actor->portablecontacts_net->addresses->formatted; - - - if (isset($actor->image->url)) - $contact["photo"] = $actor->image->url; - - if (isset($actor->image->width)) - $avatarwidth = $actor->image->width; - - if (is_array($actor->status_net->avatarLinks)) - foreach ($actor->status_net->avatarLinks AS $avatar) { - if ($avatarsize < $avatar->width) { - $contact["photo"] = $avatar->url; - $avatarsize = $avatar->width; - } - } - - update_gcontact($contact); -} - -/** - * @brief Fetches the conversation url for a given item link or conversation id - * - * @param string $self The link to the posting - * @param string $conversation_id The conversation id - * - * @return string The conversation url - */ -function ostatus_fetch_conversation($self, $conversation_id = "") { - - if ($conversation_id != "") { - $elements = explode(":", $conversation_id); + public static function convert_href($href) { + $elements = explode(":",$href); if ((count($elements) <= 2) OR ($elements[0] != "tag")) - return $conversation_id; + return $href; + + $server = explode(",", $elements[1]); + $conversation = explode("=", $elements[2]); + + if ((count($elements) == 4) AND ($elements[2] == "post")) + return "http://".$server[0]."/notice/".$elements[3]; + + if ((count($conversation) != 2) OR ($conversation[1] =="")) + return $href; + + if ($elements[3] == "objectType=thread") + return "http://".$server[0]."/conversation/".$conversation[1]; + else + return "http://".$server[0]."/notice/".$conversation[1]; + + return $href; } - if ($self == "") - return ""; + public static function check_conversations($mentions = false, $override = false) { + $last = get_config('system','ostatus_last_poll'); - $json = str_replace(".atom", ".json", $self); + $poll_interval = intval(get_config('system','ostatus_poll_interval')); + if(! $poll_interval) + $poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL; - $raw = fetch_url($json); - if ($raw == "") - return ""; + // Don't poll if the interval is set negative + if (($poll_interval < 0) AND !$override) + return; - $data = json_decode($raw); - if (!is_object($data)) - return ""; + if (!$mentions) { + $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); + if (!$poll_timeframe) + $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME; + } else { + $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); + if (!$poll_timeframe) + $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS; + } - $conversation_id = $data->statusnet_conversation_id; - $pos = strpos($self, "/api/statuses/show/"); - $base_url = substr($self, 0, $pos); + if ($last AND !$override) { + $next = $last + ($poll_interval * 60); + if ($next > time()) { + logger('poll interval not reached'); + return; + } + } - return $base_url."/conversation/".$conversation_id; -} + logger('cron_start'); -/** - * @brief Fetches actor details of a given actor and user id - * - * @param string $actor The actor url - * @param int $uid The user id - * @param int $contact_id The default contact-id - * - * @return array Array with actor details - */ -function ostatus_get_actor_details($actor, $uid, $contact_id) { + $start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60)); - $details = array(); + if ($mentions) + $conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term` + STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid` + WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention` + GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start)); + else + $conversations = q("SELECT `oid`, `url`, `uid` FROM `term` + WHERE `type` = 7 AND `term` > '%s' + GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start)); - $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", - $uid, normalise_link($actor), NETWORK_STATUSNET); + foreach ($conversations AS $conversation) { + self::completion($conversation['url'], $conversation['uid']); + } - if (!$contact) - $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'", - $uid, $actor, normalise_link($actor), NETWORK_STATUSNET); + logger('cron_end'); - if ($contact) { - logger("Found contact for url ".$actor, LOGGER_DEBUG); - $details["contact_id"] = $contact[0]["id"]; - $details["network"] = $contact[0]["network"]; - - $details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); - } else { - logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG); - - // Adding a global contact - /// @TODO Use this data for the post - $details["global_contact_id"] = get_contact($actor, 0); - - logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); - - $details["contact_id"] = $contact_id; - $details["network"] = NETWORK_OSTATUS; - - $details["not_following"] = true; + set_config('system','ostatus_last_poll', time()); } - return $details; -} + /** + * @brief Updates the gcontact table with actor data from the conversation + * + * @param object $actor The actor object that contains the contact data + */ + private function conv_fetch_actor($actor) { -function ostatus_completion($conversation_url, $uid, $item = array(), $self = "") { + // We set the generation to "3" since the data here is not as reliable as the data we get on other occasions + $contact = array("network" => NETWORK_OSTATUS, "generation" => 3); - $a = get_app(); + if (isset($actor->url)) + $contact["url"] = $actor->url; - $item_stored = -1; + if (isset($actor->displayName)) + $contact["name"] = $actor->displayName; - //$conversation_url = ostatus_convert_href($conversation_url); - $conversation_url = ostatus_fetch_conversation($self, $conversation_url); + if (isset($actor->portablecontacts_net->displayName)) + $contact["name"] = $actor->portablecontacts_net->displayName; + + if (isset($actor->portablecontacts_net->preferredUsername)) + $contact["nick"] = $actor->portablecontacts_net->preferredUsername; + + if (isset($actor->id)) + $contact["alias"] = $actor->id; + + if (isset($actor->summary)) + $contact["about"] = $actor->summary; + + if (isset($actor->portablecontacts_net->note)) + $contact["about"] = $actor->portablecontacts_net->note; + + if (isset($actor->portablecontacts_net->addresses->formatted)) + $contact["location"] = $actor->portablecontacts_net->addresses->formatted; + + + if (isset($actor->image->url)) + $contact["photo"] = $actor->image->url; + + if (isset($actor->image->width)) + $avatarwidth = $actor->image->width; + + if (is_array($actor->status_net->avatarLinks)) + foreach ($actor->status_net->avatarLinks AS $avatar) { + if ($avatarsize < $avatar->width) { + $contact["photo"] = $avatar->url; + $avatarsize = $avatar->width; + } + } + + update_gcontact($contact); + } + + /** + * @brief Fetches the conversation url for a given item link or conversation id + * + * @param string $self The link to the posting + * @param string $conversation_id The conversation id + * + * @return string The conversation url + */ + private function fetch_conversation($self, $conversation_id = "") { + + if ($conversation_id != "") { + $elements = explode(":", $conversation_id); + + if ((count($elements) <= 2) OR ($elements[0] != "tag")) + return $conversation_id; + } + + if ($self == "") + return ""; + + $json = str_replace(".atom", ".json", $self); + + $raw = fetch_url($json); + if ($raw == "") + return ""; + + $data = json_decode($raw); + if (!is_object($data)) + return ""; + + $conversation_id = $data->statusnet_conversation_id; + + $pos = strpos($self, "/api/statuses/show/"); + $base_url = substr($self, 0, $pos); + + return $base_url."/conversation/".$conversation_id; + } + + /** + * @brief Fetches actor details of a given actor and user id + * + * @param string $actor The actor url + * @param int $uid The user id + * @param int $contact_id The default contact-id + * + * @return array Array with actor details + */ + private function get_actor_details($actor, $uid, $contact_id) { + + $details = array(); + + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + $uid, normalise_link($actor), NETWORK_STATUSNET); + + if (!$contact) + $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'", + $uid, $actor, normalise_link($actor), NETWORK_STATUSNET); + + if ($contact) { + logger("Found contact for url ".$actor, LOGGER_DEBUG); + $details["contact_id"] = $contact[0]["id"]; + $details["network"] = $contact[0]["network"]; + + $details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); + } else { + logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG); + + // Adding a global contact + /// @TODO Use this data for the post + $details["global_contact_id"] = get_contact($actor, 0); + + logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); + + $details["contact_id"] = $contact_id; + $details["network"] = NETWORK_OSTATUS; + + $details["not_following"] = true; + } + + return $details; + } + + private function completion($conversation_url, $uid, $item = array(), $self = "") { + + + $item_stored = -1; + + $conversation_url = self::fetch_conversation($self, $conversation_url); + + // If the thread shouldn't be completed then store the item and go away + // Don't do a completion on liked content + if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR + ($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) { + $item_stored = item_store($item, true); + return($item_stored); + } + + // Get the parent + $parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN + (SELECT `parent` FROM `item` WHERE `id` IN + (SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))", + intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url)); + + if ($parents) + $parent = $parents[0]; + elseif (count($item) > 0) { + $parent = $item; + $parent["type"] = "remote"; + $parent["verb"] = ACTIVITY_POST; + $parent["visible"] = 1; + } else { + // Preset the parent + $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid); + if (!$r) + return(-2); + + $parent = array(); + $parent["id"] = 0; + $parent["parent"] = 0; + $parent["uri"] = ""; + $parent["contact-id"] = $r[0]["id"]; + $parent["type"] = "remote"; + $parent["verb"] = ACTIVITY_POST; + $parent["visible"] = 1; + } + + $conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as"; + $pageno = 1; + $items = array(); + + logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid); + + do { + $conv_arr = z_fetch_url($conv."?page=".$pageno); + + // If it is a non-ssl site and there is an error, then try ssl or vice versa + if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) { + $conv = str_replace("http://", "https://", $conv); + $conv_as = fetch_url($conv."?page=".$pageno); + } elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) { + $conv = str_replace("https://", "http://", $conv); + $conv_as = fetch_url($conv."?page=".$pageno); + } else + $conv_as = $conv_arr["body"]; + + $conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as); + $conv_as = json_decode($conv_as); + + $no_of_items = sizeof($items); + + if (@is_array($conv_as->items)) + foreach ($conv_as->items AS $single_item) + $items[$single_item->id] = $single_item; + + if ($no_of_items == sizeof($items)) + break; + + $pageno++; + + } while (true); + + logger('fetching conversation done. Found '.count($items).' items'); + + if (!sizeof($items)) { + if (count($item) > 0) { + $item_stored = item_store($item, true); + + if ($item_stored) { + logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG); + self::store_conversation($item_id, $conversation_url); + } + + return($item_stored); + } else + return(-3); + } + + $items = array_reverse($items); + + $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid)); + $importer = $r[0]; + + $new_parent = true; + + foreach ($items as $single_conv) { + + // Update the gcontact table + self::conv_fetch_actor($single_conv->actor); + + // Test - remove before flight + //$tempfile = tempnam(get_temppath(), "conversation"); + //file_put_contents($tempfile, json_encode($single_conv)); + + $mention = false; + + if (isset($single_conv->object->id)) + $single_conv->id = $single_conv->object->id; + + $plink = self::convert_href($single_conv->id); + if (isset($single_conv->object->url)) + $plink = self::convert_href($single_conv->object->url); + + if (@!$single_conv->id) + continue; + + logger("Got id ".$single_conv->id, LOGGER_DEBUG); + + if ($first_id == "") { + $first_id = $single_conv->id; + + // The first post of the conversation isn't our first post. There are three options: + // 1. Our conversation hasn't the "real" thread starter + // 2. This first post is a post inside our thread + // 3. This first post is a post inside another thread + if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { + + $new_parent = true; + + $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN + (SELECT `parent` FROM `item` + WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1", + intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($new_parents) { + if ($new_parents[0]["parent"] == $parent["parent"]) { + // Option 2: This post is already present inside our thread - but not as thread starter + logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG); + $first_id = $parent["uri"]; + } else { + // Option 3: Not so good. We have mixed parents. We have to see how to clean this up. + // For now just take the new parent. + $parent = $new_parents[0]; + $first_id = $parent["uri"]; + logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG); + } + } else { + // Option 1: We hadn't got the real thread starter + // We have to clean up our existing messages. + $parent["id"] = 0; + $parent["uri"] = $first_id; + logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG); + } + } elseif ($parent["uri"] == "") { + $parent["id"] = 0; + $parent["uri"] = $first_id; + } + } + + $parent_uri = $parent["uri"]; + + // "context" only seems to exist on older servers + if (isset($single_conv->context->inReplyTo->id)) { + $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", + intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($parent_exists) + $parent_uri = $single_conv->context->inReplyTo->id; + } + + // This is the current way + if (isset($single_conv->object->inReplyTo->id)) { + $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", + intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($parent_exists) + $parent_uri = $single_conv->object->inReplyTo->id; + } + + $message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", + intval($uid), dbesc($single_conv->id), + dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); + if ($message_exists) { + logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG); + + if ($parent["id"] != 0) { + $existing_message = $message_exists[0]; + + // We improved the way we fetch OStatus messages, this shouldn't happen very often now + /// @TODO We have to change the shadow copies as well. This way here is really ugly. + if ($existing_message["parent"] != $parent["id"]) { + logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG); + + // Update the parent id of the selected item + $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d", + intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"])); + + // Update the parent uri in the thread - but only if it points to itself + $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`", + dbesc($parent_uri), intval($existing_message["id"])); + + // try to change all items of the same parent + $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d", + intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"])); + + // Update the parent uri in the thread - but only if it points to itself + $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)", + dbesc($parent["uri"]), intval($existing_message["parent"])); + + // Now delete the thread + delete_thread($existing_message["parent"]); + } + } + + // The item we are having on the system is the one that we wanted to store via the item array + if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) { + $item = array(); + $item_stored = 0; + } + + continue; + } + + if (is_array($single_conv->to)) + foreach($single_conv->to AS $to) + if ($importer["nurl"] == normalise_link($to->id)) + $mention = true; + + $actor = $single_conv->actor->id; + if (isset($single_conv->actor->url)) + $actor = $single_conv->actor->url; + + $details = self::get_actor_details($actor, $uid, $parent["contact-id"]); + + // Do we only want to import threads that were started by our contacts? + if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) { + logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG); + continue; + } + + $arr = array(); + $arr["network"] = $details["network"]; + $arr["uri"] = $single_conv->id; + $arr["plink"] = $plink; + $arr["uid"] = $uid; + $arr["contact-id"] = $details["contact_id"]; + $arr["parent-uri"] = $parent_uri; + $arr["created"] = $single_conv->published; + $arr["edited"] = $single_conv->published; + $arr["owner-name"] = $single_conv->actor->displayName; + if ($arr["owner-name"] == '') + $arr["owner-name"] = $single_conv->actor->contact->displayName; + if ($arr["owner-name"] == '') + $arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName; + + $arr["owner-link"] = $actor; + $arr["owner-avatar"] = $single_conv->actor->image->url; + $arr["author-name"] = $arr["owner-name"]; + $arr["author-link"] = $actor; + $arr["author-avatar"] = $single_conv->actor->image->url; + $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content)); + + if (isset($single_conv->status_net->notice_info->source)) + $arr["app"] = strip_tags($single_conv->status_net->notice_info->source); + elseif (isset($single_conv->statusnet->notice_info->source)) + $arr["app"] = strip_tags($single_conv->statusnet->notice_info->source); + elseif (isset($single_conv->statusnet_notice_info->source)) + $arr["app"] = strip_tags($single_conv->statusnet_notice_info->source); + elseif (isset($single_conv->provider->displayName)) + $arr["app"] = $single_conv->provider->displayName; + else + $arr["app"] = "OStatus"; + + + $arr["object"] = json_encode($single_conv); + $arr["verb"] = $parent["verb"]; + $arr["visible"] = $parent["visible"]; + $arr["location"] = $single_conv->location->displayName; + $arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon); + + // Is it a reshared item? + if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) { + if (is_array($single_conv->object)) + $single_conv->object = $single_conv->object[0]; + + logger("Found reshared item ".$single_conv->object->id); + + // $single_conv->object->context->conversation; + + if (isset($single_conv->object->object->id)) + $arr["uri"] = $single_conv->object->object->id; + else + $arr["uri"] = $single_conv->object->id; + + if (isset($single_conv->object->object->url)) + $plink = self::convert_href($single_conv->object->object->url); + else + $plink = self::convert_href($single_conv->object->url); + + if (isset($single_conv->object->object->content)) + $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content)); + else + $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content)); + + $arr["plink"] = $plink; + + $arr["created"] = $single_conv->object->published; + $arr["edited"] = $single_conv->object->published; + + $arr["author-name"] = $single_conv->object->actor->displayName; + if ($arr["owner-name"] == '') + $arr["author-name"] = $single_conv->object->actor->contact->displayName; + + $arr["author-link"] = $single_conv->object->actor->url; + $arr["author-avatar"] = $single_conv->object->actor->image->url; + + $arr["app"] = $single_conv->object->provider->displayName."#"; + //$arr["verb"] = $single_conv->object->verb; + + $arr["location"] = $single_conv->object->location->displayName; + $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon); + } + + if ($arr["location"] == "") + unset($arr["location"]); + + if ($arr["coord"] == "") + unset($arr["coord"]); + + // Copy fields from given item array + if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] == $single_conv->id))) { + $copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar", + "gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag", + "title", "attach", "app", "type", "location", "contact-id", "uri"); + foreach ($copy_fields AS $field) + if (isset($item[$field])) + $arr[$field] = $item[$field]; + + } + + $newitem = item_store($arr); + if (!$newitem) { + logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG); + continue; + } + + if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) { + $item = array(); + $item_stored = $newitem; + } + + logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG); + + // Add the conversation entry (but don't fetch the whole conversation) + self::store_conversation($newitem, $conversation_url); + + // If the newly created item is the top item then change the parent settings of the thread + // This shouldn't happen anymore. This is supposed to be absolote. + if ($arr["uri"] == $first_id) { + logger('setting new parent to id '.$newitem); + $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", + intval($uid), intval($newitem)); + if ($new_parents) + $parent = $new_parents[0]; + } + } + + if (($item_stored < 0) AND (count($item) > 0)) { + + if (get_config('system','ostatus_full_threads')) { + $details = self::get_actor_details($item["owner-link"], $uid, $item["contact-id"]); + if ($details["not_following"]) { + logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG); + return false; + } + } + + $item_stored = item_store($item, true); + if ($item_stored) { + logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG); + self::store_conversation($item_stored, $conversation_url); + } + } - // If the thread shouldn't be completed then store the item and go away - // Don't do a completion on liked content - if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR - ($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) { - //$arr["app"] .= " (OStatus-NoCompletion)"; - $item_stored = item_store($item, true); return($item_stored); } - // Get the parent - $parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN - (SELECT `parent` FROM `item` WHERE `id` IN - (SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))", - intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url)); + private function store_conversation($itemid, $conversation_url) { - if ($parents) - $parent = $parents[0]; - elseif (count($item) > 0) { - $parent = $item; - $parent["type"] = "remote"; - $parent["verb"] = ACTIVITY_POST; - $parent["visible"] = 1; - } else { - // Preset the parent - $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid); - if (!$r) - return(-2); + $conversation_url = self::convert_href($conversation_url); - $parent = array(); - $parent["id"] = 0; - $parent["parent"] = 0; - $parent["uri"] = ""; - $parent["contact-id"] = $r[0]["id"]; - $parent["type"] = "remote"; - $parent["verb"] = ACTIVITY_POST; - $parent["visible"] = 1; + $messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid)); + if (!$messages) + return; + $message = $messages[0]; + + // Store conversation url if not done before + $conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d", + intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION)); + + if (!$conversation) { + $r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", + intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), + dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"])); + logger('Storing conversation url '.$conversation_url.' for id '.$itemid); + } } - $conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as"; - $pageno = 1; - $items = array(); + private function get_reshared_guid($item) { + $body = trim($item["body"]); - logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid); + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(""); - do { - $conv_arr = z_fetch_url($conv."?page=".$pageno); + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(""); - // If it is a non-ssl site and there is an error, then try ssl or vice versa - if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) { - $conv = str_replace("http://", "https://", $conv); - $conv_as = fetch_url($conv."?page=".$pageno); - } elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) { - $conv = str_replace("https://", "http://", $conv); - $conv_as = fetch_url($conv."?page=".$pageno); - } else - $conv_as = $conv_arr["body"]; + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + // Skip if there is no shared message in there + if ($body == $attributes) + return(false); - $conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as); - $conv_as = json_decode($conv_as); + $guid = ""; + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; - $no_of_items = sizeof($items); + preg_match('/guid="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; - if (@is_array($conv_as->items)) - foreach ($conv_as->items AS $single_item) - $items[$single_item->id] = $single_item; - - if ($no_of_items == sizeof($items)) - break; - - $pageno++; - - } while (true); - - logger('fetching conversation done. Found '.count($items).' items'); - - if (!sizeof($items)) { - if (count($item) > 0) { - //$arr["app"] .= " (OStatus-NoConvFetched)"; - $item_stored = item_store($item, true); - - if ($item_stored) { - logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG); - ostatus_store_conversation($item_id, $conversation_url); - } - - return($item_stored); - } else - return(-3); + return $guid; } - $items = array_reverse($items); + private function format_picture_post($body) { + $siteinfo = get_attached_data($body); - $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid)); - $importer = $r[0]; + if (($siteinfo["type"] == "photo")) { + if (isset($siteinfo["preview"])) + $preview = $siteinfo["preview"]; + else + $preview = $siteinfo["image"]; - $new_parent = true; + // Is it a remote picture? Then make a smaller preview here + $preview = proxy_url($preview, false, PROXY_SIZE_SMALL); - foreach ($items as $single_conv) { + // Is it a local picture? Then make it smaller here + $preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview); + $preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview); - // Update the gcontact table - ostatus_conv_fetch_actor($single_conv->actor); + if (isset($siteinfo["url"])) + $url = $siteinfo["url"]; + else + $url = $siteinfo["image"]; - // Test - remove before flight - //$tempfile = tempnam(get_temppath(), "conversation"); - //file_put_contents($tempfile, json_encode($single_conv)); - - $mention = false; - - if (isset($single_conv->object->id)) - $single_conv->id = $single_conv->object->id; - - $plink = ostatus_convert_href($single_conv->id); - if (isset($single_conv->object->url)) - $plink = ostatus_convert_href($single_conv->object->url); - - if (@!$single_conv->id) - continue; - - logger("Got id ".$single_conv->id, LOGGER_DEBUG); - - if ($first_id == "") { - $first_id = $single_conv->id; - - // The first post of the conversation isn't our first post. There are three options: - // 1. Our conversation hasn't the "real" thread starter - // 2. This first post is a post inside our thread - // 3. This first post is a post inside another thread - if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { - - $new_parent = true; - - $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN - (SELECT `parent` FROM `item` - WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1", - intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($new_parents) { - if ($new_parents[0]["parent"] == $parent["parent"]) { - // Option 2: This post is already present inside our thread - but not as thread starter - logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG); - $first_id = $parent["uri"]; - } else { - // Option 3: Not so good. We have mixed parents. We have to see how to clean this up. - // For now just take the new parent. - $parent = $new_parents[0]; - $first_id = $parent["uri"]; - logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG); - } - } else { - // Option 1: We hadn't got the real thread starter - // We have to clean up our existing messages. - $parent["id"] = 0; - $parent["uri"] = $first_id; - logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG); - } - } elseif ($parent["uri"] == "") { - $parent["id"] = 0; - $parent["uri"] = $first_id; - } + $body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]"; } - $parent_uri = $parent["uri"]; + return $body; + } - // "context" only seems to exist on older servers - if (isset($single_conv->context->inReplyTo->id)) { - $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", - intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($parent_exists) - $parent_uri = $single_conv->context->inReplyTo->id; - } + private function add_header($doc, $owner) { - // This is the current way - if (isset($single_conv->object->inReplyTo->id)) { - $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", - intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($parent_exists) - $parent_uri = $single_conv->object->inReplyTo->id; - } + $a = get_app(); - $message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", - intval($uid), dbesc($single_conv->id), - dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($message_exists) { - logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG); + $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); + $doc->appendChild($root); - if ($parent["id"] != 0) { - $existing_message = $message_exists[0]; + $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $root->setAttribute("xmlns:poco", NAMESPACE_POCO); + $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); - // We improved the way we fetch OStatus messages, this shouldn't happen very often now - /// @TODO We have to change the shadow copies as well. This way here is really ugly. - if ($existing_message["parent"] != $parent["id"]) { - logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG); + $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); + xml::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); + xml::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]); + xml::add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"])); + xml::add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"])); + xml::add_element($doc, $root, "logo", $owner["photo"]); + xml::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); - // Update the parent id of the selected item - $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d", - intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"])); + $author = self::add_author($doc, $owner); + $root->appendChild($author); - // Update the parent uri in the thread - but only if it points to itself - $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`", - dbesc($parent_uri), intval($existing_message["id"])); + $attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html"); + xml::add_element($doc, $root, "link", "", $attributes); - // try to change all items of the same parent - $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d", - intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"])); + /// @TODO We have to find out what this is + /// $attributes = array("href" => App::get_baseurl()."/sup", + /// "rel" => "http://api.friendfeed.com/2008/03#sup", + /// "type" => "application/json"); + /// xml::add_element($doc, $root, "link", "", $attributes); - // Update the parent uri in the thread - but only if it points to itself - $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)", - dbesc($parent["uri"]), intval($existing_message["parent"])); + self::hublinks($doc, $root); - // Now delete the thread - delete_thread($existing_message["parent"]); + $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon"); + xml::add_element($doc, $root, "link", "", $attributes); + + $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies"); + xml::add_element($doc, $root, "link", "", $attributes); + + $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention"); + xml::add_element($doc, $root, "link", "", $attributes); + + $attributes = array("href" => App::get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom", + "rel" => "self", "type" => "application/atom+xml"); + xml::add_element($doc, $root, "link", "", $attributes); + + return $root; + } + + public static function hublinks($doc, $root) { + $hub = get_config('system','huburl'); + + $hubxml = ''; + if(strlen($hub)) { + $hubs = explode(',', $hub); + if(count($hubs)) { + foreach($hubs as $h) { + $h = trim($h); + if(! strlen($h)) + continue; + if ($h === '[internal]') + $h = App::get_baseurl() . '/pubsubhubbub'; + xml::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub")); } } - - // The item we are having on the system is the one that we wanted to store via the item array - if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) { - $item = array(); - $item_stored = 0; - } - - continue; - } - - if (is_array($single_conv->to)) - foreach($single_conv->to AS $to) - if ($importer["nurl"] == normalise_link($to->id)) - $mention = true; - - $actor = $single_conv->actor->id; - if (isset($single_conv->actor->url)) - $actor = $single_conv->actor->url; - - $details = ostatus_get_actor_details($actor, $uid, $parent["contact-id"]); - - // Do we only want to import threads that were started by our contacts? - if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) { - logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG); - continue; - } - - $arr = array(); - $arr["network"] = $details["network"]; - $arr["uri"] = $single_conv->id; - $arr["plink"] = $plink; - $arr["uid"] = $uid; - $arr["contact-id"] = $details["contact_id"]; - $arr["parent-uri"] = $parent_uri; - $arr["created"] = $single_conv->published; - $arr["edited"] = $single_conv->published; - $arr["owner-name"] = $single_conv->actor->displayName; - if ($arr["owner-name"] == '') - $arr["owner-name"] = $single_conv->actor->contact->displayName; - if ($arr["owner-name"] == '') - $arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName; - - $arr["owner-link"] = $actor; - $arr["owner-avatar"] = $single_conv->actor->image->url; - $arr["author-name"] = $arr["owner-name"]; - $arr["author-link"] = $actor; - $arr["author-avatar"] = $single_conv->actor->image->url; - $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content)); - - if (isset($single_conv->status_net->notice_info->source)) - $arr["app"] = strip_tags($single_conv->status_net->notice_info->source); - elseif (isset($single_conv->statusnet->notice_info->source)) - $arr["app"] = strip_tags($single_conv->statusnet->notice_info->source); - elseif (isset($single_conv->statusnet_notice_info->source)) - $arr["app"] = strip_tags($single_conv->statusnet_notice_info->source); - elseif (isset($single_conv->provider->displayName)) - $arr["app"] = $single_conv->provider->displayName; - else - $arr["app"] = "OStatus"; - - //$arr["app"] .= " (Conversation)"; - - $arr["object"] = json_encode($single_conv); - $arr["verb"] = $parent["verb"]; - $arr["visible"] = $parent["visible"]; - $arr["location"] = $single_conv->location->displayName; - $arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon); - - // Is it a reshared item? - if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) { - if (is_array($single_conv->object)) - $single_conv->object = $single_conv->object[0]; - - logger("Found reshared item ".$single_conv->object->id); - - // $single_conv->object->context->conversation; - - if (isset($single_conv->object->object->id)) - $arr["uri"] = $single_conv->object->object->id; - else - $arr["uri"] = $single_conv->object->id; - - if (isset($single_conv->object->object->url)) - $plink = ostatus_convert_href($single_conv->object->object->url); - else - $plink = ostatus_convert_href($single_conv->object->url); - - if (isset($single_conv->object->object->content)) - $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content)); - else - $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content)); - - $arr["plink"] = $plink; - - $arr["created"] = $single_conv->object->published; - $arr["edited"] = $single_conv->object->published; - - $arr["author-name"] = $single_conv->object->actor->displayName; - if ($arr["owner-name"] == '') - $arr["author-name"] = $single_conv->object->actor->contact->displayName; - - $arr["author-link"] = $single_conv->object->actor->url; - $arr["author-avatar"] = $single_conv->object->actor->image->url; - - $arr["app"] = $single_conv->object->provider->displayName."#"; - //$arr["verb"] = $single_conv->object->verb; - - $arr["location"] = $single_conv->object->location->displayName; - $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon); - } - - if ($arr["location"] == "") - unset($arr["location"]); - - if ($arr["coord"] == "") - unset($arr["coord"]); - - // Copy fields from given item array - if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] == $single_conv->id))) { - $copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar", - "gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag", - "title", "attach", "app", "type", "location", "contact-id", "uri"); - foreach ($copy_fields AS $field) - if (isset($item[$field])) - $arr[$field] = $item[$field]; - - //$arr["app"] .= " (OStatus)"; - } - - $newitem = item_store($arr); - if (!$newitem) { - logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG); - continue; - } - - if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) { - $item = array(); - $item_stored = $newitem; - } - - logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG); - - // Add the conversation entry (but don't fetch the whole conversation) - ostatus_store_conversation($newitem, $conversation_url); - - // If the newly created item is the top item then change the parent settings of the thread - // This shouldn't happen anymore. This is supposed to be absolote. - if ($arr["uri"] == $first_id) { - logger('setting new parent to id '.$newitem); - $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", - intval($uid), intval($newitem)); - if ($new_parents) - $parent = $new_parents[0]; } } - if (($item_stored < 0) AND (count($item) > 0)) { - //$arr["app"] .= " (OStatus-NoConvFound)"; + private function get_attachment($doc, $root, $item) { + $o = ""; + $siteinfo = get_attached_data($item["body"]); - if (get_config('system','ostatus_full_threads')) { - $details = ostatus_get_actor_details($item["owner-link"], $uid, $item["contact-id"]); - if ($details["not_following"]) { - logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG); - return false; - } - } - - $item_stored = item_store($item, true); - if ($item_stored) { - logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG); - ostatus_store_conversation($item_stored, $conversation_url); - } - } - - return($item_stored); -} - -function ostatus_store_conversation($itemid, $conversation_url) { - global $a; - - $conversation_url = ostatus_convert_href($conversation_url); - - $messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid)); - if (!$messages) - return; - $message = $messages[0]; - - // Store conversation url if not done before - $conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d", - intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION)); - - if (!$conversation) { - $r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", - intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), - dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"])); - logger('Storing conversation url '.$conversation_url.' for id '.$itemid); - } -} - -function get_reshared_guid($item) { - $body = trim($item["body"]); - - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(""); - - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(""); - - $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); - // Skip if there is no shared message in there - if ($body == $attributes) - return(false); - - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - return $guid; -} - -function xml_create_element($doc, $element, $value = "", $attributes = array()) { - $element = $doc->createElement($element, xmlify($value)); - - foreach ($attributes AS $key => $value) { - $attribute = $doc->createAttribute($key); - $attribute->value = xmlify($value); - $element->appendChild($attribute); - } - return $element; -} - -function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) { - $element = xml_create_element($doc, $element, $value, $attributes); - $parent->appendChild($element); -} - -function ostatus_format_picture_post($body) { - $siteinfo = get_attached_data($body); - - if (($siteinfo["type"] == "photo")) { - if (isset($siteinfo["preview"])) - $preview = $siteinfo["preview"]; - else - $preview = $siteinfo["image"]; - - // Is it a remote picture? Then make a smaller preview here - $preview = proxy_url($preview, false, PROXY_SIZE_SMALL); - - // Is it a local picture? Then make it smaller here - $preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview); - $preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview); - - if (isset($siteinfo["url"])) - $url = $siteinfo["url"]; - else - $url = $siteinfo["image"]; - - $body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]"; - } - - return $body; -} - -function ostatus_add_header($doc, $owner) { - $a = get_app(); - - $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); - $doc->appendChild($root); - - $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); - $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); - $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); - $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); - $root->setAttribute("xmlns:poco", NAMESPACE_POCO); - $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); - $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); - - $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); - xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); - xml_add_element($doc, $root, "id", $a->get_baseurl()."/profile/".$owner["nick"]); - xml_add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"])); - xml_add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"])); - xml_add_element($doc, $root, "logo", $owner["photo"]); - xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); - - $author = ostatus_add_author($doc, $owner); - $root->appendChild($author); - - $attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html"); - xml_add_element($doc, $root, "link", "", $attributes); - - /// @TODO We have to find out what this is - /// $attributes = array("href" => $a->get_baseurl()."/sup", - /// "rel" => "http://api.friendfeed.com/2008/03#sup", - /// "type" => "application/json"); - /// xml_add_element($doc, $root, "link", "", $attributes); - - ostatus_hublinks($doc, $root); - - $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon"); - xml_add_element($doc, $root, "link", "", $attributes); - - $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies"); - xml_add_element($doc, $root, "link", "", $attributes); - - $attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention"); - xml_add_element($doc, $root, "link", "", $attributes); - - $attributes = array("href" => $a->get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom", - "rel" => "self", "type" => "application/atom+xml"); - xml_add_element($doc, $root, "link", "", $attributes); - - return $root; -} - -function ostatus_hublinks($doc, $root) { - $a = get_app(); - $hub = get_config('system','huburl'); - - $hubxml = ''; - if(strlen($hub)) { - $hubs = explode(',', $hub); - if(count($hubs)) { - foreach($hubs as $h) { - $h = trim($h); - if(! strlen($h)) - continue; - if ($h === '[internal]') - $h = $a->get_baseurl() . '/pubsubhubbub'; - xml_add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub")); - } - } - } -} - -function ostatus_get_attachment($doc, $root, $item) { - $o = ""; - $siteinfo = get_attached_data($item["body"]); - - switch($siteinfo["type"]) { - case 'link': - $attributes = array("rel" => "enclosure", - "href" => $siteinfo["url"], - "type" => "text/html; charset=UTF-8", - "length" => "", - "title" => $siteinfo["title"]); - xml_add_element($doc, $root, "link", "", $attributes); - break; - case 'photo': - $imgdata = get_photo_info($siteinfo["image"]); - $attributes = array("rel" => "enclosure", - "href" => $siteinfo["image"], - "type" => $imgdata["mime"], - "length" => intval($imgdata["size"])); - xml_add_element($doc, $root, "link", "", $attributes); - break; - case 'video': - $attributes = array("rel" => "enclosure", - "href" => $siteinfo["url"], - "type" => "text/html; charset=UTF-8", - "length" => "", - "title" => $siteinfo["title"]); - xml_add_element($doc, $root, "link", "", $attributes); - break; - default: - break; - } - - if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) { - $photodata = get_photo_info($siteinfo["image"]); - - $attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]); - xml_add_element($doc, $root, "link", "", $attributes); - } - - - $arr = explode('[/attach],',$item['attach']); - if(count($arr)) { - foreach($arr as $r) { - $matches = false; - $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches); - if($cnt) { + switch($siteinfo["type"]) { + case 'link': $attributes = array("rel" => "enclosure", - "href" => $matches[1], - "type" => $matches[3]); + "href" => $siteinfo["url"], + "type" => "text/html; charset=UTF-8", + "length" => "", + "title" => $siteinfo["title"]); + xml::add_element($doc, $root, "link", "", $attributes); + break; + case 'photo': + $imgdata = get_photo_info($siteinfo["image"]); + $attributes = array("rel" => "enclosure", + "href" => $siteinfo["image"], + "type" => $imgdata["mime"], + "length" => intval($imgdata["size"])); + xml::add_element($doc, $root, "link", "", $attributes); + break; + case 'video': + $attributes = array("rel" => "enclosure", + "href" => $siteinfo["url"], + "type" => "text/html; charset=UTF-8", + "length" => "", + "title" => $siteinfo["title"]); + xml::add_element($doc, $root, "link", "", $attributes); + break; + default: + break; + } - if(intval($matches[2])) - $attributes["length"] = intval($matches[2]); + if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) { + $photodata = get_photo_info($siteinfo["image"]); - if(trim($matches[4]) != "") - $attributes["title"] = trim($matches[4]); + $attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]); + xml::add_element($doc, $root, "link", "", $attributes); + } - xml_add_element($doc, $root, "link", "", $attributes); + + $arr = explode('[/attach],',$item['attach']); + if(count($arr)) { + foreach($arr as $r) { + $matches = false; + $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches); + if($cnt) { + $attributes = array("rel" => "enclosure", + "href" => $matches[1], + "type" => $matches[3]); + + if(intval($matches[2])) + $attributes["length"] = intval($matches[2]); + + if(trim($matches[4]) != "") + $attributes["title"] = trim($matches[4]); + + xml::add_element($doc, $root, "link", "", $attributes); + } } } } -} -function ostatus_add_author($doc, $owner) { - $a = get_app(); + private function add_author($doc, $owner) { - $r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"])); - if ($r) - $profile = $r[0]; + $r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"])); + if ($r) + $profile = $r[0]; - $author = $doc->createElement("author"); - xml_add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON); - xml_add_element($doc, $author, "uri", $owner["url"]); - xml_add_element($doc, $author, "name", $owner["name"]); + $author = $doc->createElement("author"); + xml::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON); + xml::add_element($doc, $author, "uri", $owner["url"]); + xml::add_element($doc, $author, "name", $owner["name"]); + xml::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7)); - $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]); - xml_add_element($doc, $author, "link", "", $attributes); + $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]); + xml::add_element($doc, $author, "link", "", $attributes); - $attributes = array( - "rel" => "avatar", - "type" => "image/jpeg", // To-Do? - "media:width" => 175, - "media:height" => 175, - "href" => $owner["photo"]); - xml_add_element($doc, $author, "link", "", $attributes); - - if (isset($owner["thumb"])) { $attributes = array( "rel" => "avatar", "type" => "image/jpeg", // To-Do? - "media:width" => 80, - "media:height" => 80, - "href" => $owner["thumb"]); - xml_add_element($doc, $author, "link", "", $attributes); + "media:width" => 175, + "media:height" => 175, + "href" => $owner["photo"]); + xml::add_element($doc, $author, "link", "", $attributes); + + if (isset($owner["thumb"])) { + $attributes = array( + "rel" => "avatar", + "type" => "image/jpeg", // To-Do? + "media:width" => 80, + "media:height" => 80, + "href" => $owner["thumb"]); + xml::add_element($doc, $author, "link", "", $attributes); + } + + xml::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]); + xml::add_element($doc, $author, "poco:displayName", $owner["name"]); + xml::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7)); + + if (trim($owner["location"]) != "") { + $element = $doc->createElement("poco:address"); + xml::add_element($doc, $element, "poco:formatted", $owner["location"]); + $author->appendChild($element); + } + + if (trim($profile["homepage"]) != "") { + $urls = $doc->createElement("poco:urls"); + xml::add_element($doc, $urls, "poco:type", "homepage"); + xml::add_element($doc, $urls, "poco:value", $profile["homepage"]); + xml::add_element($doc, $urls, "poco:primary", "true"); + $author->appendChild($urls); + } + + if (count($profile)) { + xml::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"])); + xml::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"])); + } + + return $author; } - xml_add_element($doc, $author, "poco:preferredUsername", $owner["nick"]); - xml_add_element($doc, $author, "poco:displayName", $owner["name"]); - xml_add_element($doc, $author, "poco:note", $owner["about"]); + /** + * @TODO Picture attachments should look like this: + * <a href="https://status.pirati.ca/attachment/572819" title="https://status.pirati.ca/file/heluecht-20151202T222602-rd3u49p.gif" + * class="attachment thumbnail" id="attachment-572819" rel="nofollow external">https://status.pirati.ca/attachment/572819</a> + * + */ - if (trim($owner["location"]) != "") { - $element = $doc->createElement("poco:address"); - xml_add_element($doc, $element, "poco:formatted", $owner["location"]); - $author->appendChild($element); + function construct_verb($item) { + if ($item['verb']) + return $item['verb']; + return ACTIVITY_POST; } - if (trim($profile["homepage"]) != "") { - $urls = $doc->createElement("poco:urls"); - xml_add_element($doc, $urls, "poco:type", "homepage"); - xml_add_element($doc, $urls, "poco:value", $profile["homepage"]); - xml_add_element($doc, $urls, "poco:primary", "true"); - $author->appendChild($urls); + function construct_objecttype($item) { + if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT))) + return $item['object-type']; + return ACTIVITY_OBJ_NOTE; } - if (count($profile)) { - xml_add_element($doc, $author, "followers", "", array("url" => $a->get_baseurl()."/viewcontacts/".$owner["nick"])); - xml_add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"])); + private function entry($doc, $item, $owner, $toplevel = false) { + $repeated_guid = self::get_reshared_guid($item); + if ($repeated_guid != "") + $xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel); + + if ($xml) + return $xml; + + if ($item["verb"] == ACTIVITY_LIKE) + return self::like_entry($doc, $item, $owner, $toplevel); + else + return self::note_entry($doc, $item, $owner, $toplevel); } - return $author; -} + private function source_entry($doc, $contact) { + $source = $doc->createElement("source"); + xml::add_element($doc, $source, "id", $contact["poll"]); + xml::add_element($doc, $source, "title", $contact["name"]); + xml::add_element($doc, $source, "link", "", array("rel" => "alternate", + "type" => "text/html", + "href" => $contact["alias"])); + xml::add_element($doc, $source, "link", "", array("rel" => "self", + "type" => "application/atom+xml", + "href" => $contact["poll"])); + xml::add_element($doc, $source, "icon", $contact["photo"]); + xml::add_element($doc, $source, "updated", datetime_convert("UTC","UTC",$contact["success_update"]."+00:00",ATOM_TIME)); -/** - * @TODO Picture attachments should look like this: - * <a href="https://status.pirati.ca/attachment/572819" title="https://status.pirati.ca/file/heluecht-20151202T222602-rd3u49p.gif" - * class="attachment thumbnail" id="attachment-572819" rel="nofollow external">https://status.pirati.ca/attachment/572819</a> - * -*/ - -function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) { - $a = get_app(); - - if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { - logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + return $source; } - $is_repeat = false; + private function contact_entry($url, $owner) { -/* if (!$repeat) { - $repeated_guid = get_reshared_guid($item); + $r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1", + dbesc(normalise_link($url)), intval($owner["uid"])); + if ($r) { + $contact = $r[0]; + $contact["uid"] = -1; + } - if ($repeated_guid != "") { - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($owner["uid"]), dbesc($repeated_guid)); + if (!$r) { + $r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1", + dbesc(normalise_link($url))); if ($r) { - $repeated_item = $r[0]; - $is_repeat = true; + $contact = $r[0]; + $contact["uid"] = -1; + $contact["success_update"] = $contact["updated"]; } } - } -*/ - if (!$toplevel AND !$repeat) { - $entry = $doc->createElement("entry"); - $title = sprintf("New note by %s", $owner["nick"]); - } elseif (!$toplevel AND $repeat) { - $entry = $doc->createElement("activity:object"); - $title = sprintf("New note by %s", $owner["nick"]); - } else { - $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry"); - $entry->setAttribute("xmlns:thr", NAMESPACE_THREAD); - $entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS); - $entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); - $entry->setAttribute("xmlns:media", NAMESPACE_MEDIA); - $entry->setAttribute("xmlns:poco", NAMESPACE_POCO); - $entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); - $entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); + if (!$r) + $contact = owner; - $author = ostatus_add_author($doc, $owner); - $entry->appendChild($author); - - $title = sprintf("New comment by %s", $owner["nick"]); - } - - // To use the object-type "bookmark" we have to implement these elements: - // - // <activity:object-type>http://activitystrea.ms/schema/1.0/bookmark</activity:object-type> - // <title>Historic Rocket Landing</title> - // <summary>Nur ein Testbeitrag.</summary> - // <link rel="related" href="https://www.youtube.com/watch?v=9pillaOxGCo"/> - // <link rel="preview" href="https://pirati.cc/file/thumb-4526-450x338-b48c8055f0c2fed0c3f67adc234c4b99484a90c42ed3cac73dc1081a4d0a7bc1.jpg.jpg" media:width="450" media:height="338"/> - // - // But: it seems as if it doesn't federate well between the GS servers - // So we just set it to "note" to be sure that it reaches their target systems - - if (!$repeat) - xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); - else - xml_add_element($doc, $entry, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA.'activity'); - - xml_add_element($doc, $entry, "id", $item["uri"]); - xml_add_element($doc, $entry, "title", $title); - - if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) - $body = fix_private_photos($item['body'],$owner['uid'],$item, 0); - else - $body = $item['body']; - - $body = ostatus_format_picture_post($body); - - if ($item['title'] != "") - $body = "[b]".$item['title']."[/b]\n\n".$body; - - //$body = bb_remove_share_information($body); - $body = bbcode($body, false, false, 7); - - xml_add_element($doc, $entry, "content", $body, array("type" => "html")); - - xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", - "href" => $a->get_baseurl()."/display/".$item["guid"])); - - xml_add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"])); - - if (!$is_repeat) - xml_add_element($doc, $entry, "activity:verb", construct_verb($item)); - else - xml_add_element($doc, $entry, "activity:verb", ACTIVITY_SHARE); - - xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); - xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); - - if ($is_repeat) { - $repeated_owner = array(); - $repeated_owner["name"] = $repeated_item["author-name"]; - $repeated_owner["url"] = $repeated_item["author-link"]; - $repeated_owner["photo"] = $repeated_item["author-avatar"]; - $repeated_owner["nick"] = $repeated_owner["name"]; - $repeated_owner["location"] = ""; - $repeated_owner["about"] = ""; - $repeated_owner["uid"] = 0; - - // Fetch the missing data from the global contacts - $r =q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", normalise_link($repeated_item["author-link"])); - if ($r) { - if ($r[0]["nick"] != "") - $repeated_owner["nick"] = $r[0]["nick"]; - - $repeated_owner["location"] = $r[0]["location"]; - $repeated_owner["about"] = $r[0]["about"]; + if (!isset($contact["poll"])) { + $data = probe_url($url); + $contact["alias"] = $data["alias"]; + $contact["poll"] = $data["poll"]; } - $entry_repeat = ostatus_entry($doc, $repeated_item, $repeated_owner, false, true); - $entry->appendChild($entry_repeat); - } elseif ($repeat) { - $author = ostatus_add_author($doc, $owner); - $entry->appendChild($author); + if (!isset($contact["alias"])) + $contact["alias"] = $contact["url"]; + + return $contact; } - $mentioned = array(); + private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) { + + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + + $title = self::entry_header($doc, $entry, $owner, $toplevel); + + $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' AND NOT `private` AND `network` IN ('%s', '%s', '%s') LIMIT 1", + intval($owner["uid"]), dbesc($repeated_guid), + dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); + if ($r) + $repeated_item = $r[0]; + else + return false; + + $contact = self::contact_entry($repeated_item['author-link'], $owner); - if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { - $parent = q("SELECT `guid`, `author-link`, `owner-link` 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" => $a->get_baseurl()."/display/".$parent[0]["guid"]); - xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes); + $title = $owner["nick"]." repeated a notice by ".$contact["nick"]; - $attributes = array( - "rel" => "related", - "href" => $a->get_baseurl()."/display/".$parent[0]["guid"]); - xml_add_element($doc, $entry, "link", "", $attributes); + self::entry_content($doc, $entry, $item, $owner, $title, ACTIVITY_SHARE, false); - $mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"]; - $mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"]; + $as_object = $doc->createElement("activity:object"); - $thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", +// ostatusWaEeYs +// ostatusogu9zg - besser + xml::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity"); + + self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false); + + $author = self::add_author($doc, $contact); + $as_object->appendChild($author); + + $as_object2 = $doc->createElement("activity:object"); + + xml::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item)); + + $title = sprintf("New comment by %s", $contact["nick"]); + + self::entry_content($doc, $as_object2, $repeated_item, $owner, $title); + + $as_object->appendChild($as_object2); + + self::entry_footer($doc, $as_object, $item, $owner, false); + + $source = self::source_entry($doc, $contact); + + $as_object->appendChild($source); + + $entry->appendChild($as_object); + + self::entry_footer($doc, $entry, $item, $owner); + + return $entry; + } + + private function like_entry($doc, $item, $owner, $toplevel) { + + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + + $title = self::entry_header($doc, $entry, $owner, $toplevel); + + $verb = NAMESPACE_ACTIVITY_SCHEMA."favorite"; + self::entry_content($doc, $entry, $item, $owner, "Favorite", $verb, false); + + $as_object = $doc->createElement("activity:object"); + + $parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"])); + $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); + + xml::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0])); + + self::entry_content($doc, $as_object, $parent[0], $owner, "New entry"); + + $entry->appendChild($as_object); + + self::entry_footer($doc, $entry, $item, $owner); + + return $entry; + } + + private function note_entry($doc, $item, $owner, $toplevel) { + + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + + $title = self::entry_header($doc, $entry, $owner, $toplevel); + + xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); + + self::entry_content($doc, $entry, $item, $owner, $title); + + self::entry_footer($doc, $entry, $item, $owner); + + return $entry; + } + + private function entry_header($doc, &$entry, $owner, $toplevel) { + if (!$toplevel) { + $entry = $doc->createElement("entry"); + $title = sprintf("New note by %s", $owner["nick"]); + } else { + $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry"); + + $entry->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $entry->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $entry->setAttribute("xmlns:poco", NAMESPACE_POCO); + $entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); + + $author = self::add_author($doc, $owner); + $entry->appendChild($author); + + $title = sprintf("New comment by %s", $owner["nick"]); + } + return $title; + } + + private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) { + + if ($verb == "") + $verb = self::construct_verb($item); + + xml::add_element($doc, $entry, "id", $item["uri"]); + xml::add_element($doc, $entry, "title", $title); + + $body = self::format_picture_post($item['body']); + + if ($item['title'] != "") + $body = "[b]".$item['title']."[/b]\n\n".$body; + + $body = bbcode($body, false, false, 7); + + xml::add_element($doc, $entry, "content", $body, array("type" => "html")); + + xml::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", + "href" => App::get_baseurl()."/display/".$item["guid"])); + + if ($complete) + xml::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"])); + + xml::add_element($doc, $entry, "activity:verb", $verb); + + xml::add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); + xml::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); + } + + private function entry_footer($doc, $entry, $item, $owner, $complete = true) { + + $mentioned = array(); + + if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { + $parent = q("SELECT `guid`, `author-link`, `owner-link` 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"]); + xml::add_element($doc, $entry, "thr:in-reply-to", "", $attributes); + + $attributes = array( + "rel" => "related", + "href" => App::get_baseurl()."/display/".$parent[0]["guid"]); + xml::add_element($doc, $entry, "link", "", $attributes); + + $mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"]; + $mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"]; + + $thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($owner["uid"]), + dbesc($parent_item)); + if ($thrparent) { + $mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"]; + $mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"]; + } + } + + xml::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation", + "href" => App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"])); + xml::add_element($doc, $entry, "ostatus:conversation", App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]); + + $tags = item_getfeedtags($item); + + if(count($tags)) + foreach($tags as $t) + if ($t[0] == "@") + $mentioned[$t[1]] = $t[1]; + + // Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS) + $newmentions = array(); + foreach ($mentioned AS $mention) { + $newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention); + $newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention); + } + $mentioned = $newmentions; + + foreach ($mentioned AS $mention) { + $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", intval($owner["uid"]), - dbesc($parent_item)); - if ($thrparent) { - $mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"]; - $mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"]; + dbesc(normalise_link($mention))); + if ($r[0]["forum"] OR $r[0]["prv"]) + xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_GROUP, + "href" => $mention)); + else + xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_PERSON, + "href" => $mention)); + } + + if (!$item["private"]) { + xml::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention", + "href" => "http://activityschema.org/collection/public")); + xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection", + "href" => "http://activityschema.org/collection/public")); + } + + if(count($tags)) + foreach($tags as $t) + if ($t[0] != "@") + xml::add_element($doc, $entry, "category", "", array("term" => $t[2])); + + self::get_attachment($doc, $entry, $item); + + if ($complete) { + $app = $item["app"]; + if ($app == "") + $app = "web"; + + $attributes = array("local_id" => $item["id"], "source" => $app); + + if (isset($parent["id"])) + $attributes["repeat_of"] = $parent["id"]; + + if ($item["coord"] != "") + xml::add_element($doc, $entry, "georss:point", $item["coord"]); + + xml::add_element($doc, $entry, "statusnet:notice_info", "", $attributes); } } - xml_add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation", - "href" => $a->get_baseurl()."/display/".$owner["nick"]."/".$item["parent"])); - xml_add_element($doc, $entry, "ostatus:conversation", $a->get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]); + public static function feed(&$a, $owner_nick, $last_update) { - $tags = item_getfeedtags($item); + $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` + FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` + WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1", + dbesc($owner_nick)); + if (!$r) + return; - if(count($tags)) - foreach($tags as $t) - if ($t[0] == "@") - $mentioned[$t[1]] = $t[1]; + $owner = $r[0]; - // Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS) - $newmentions = array(); - foreach ($mentioned AS $mention) { - $newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention); - $newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention); - } - $mentioned = $newmentions; + if(!strlen($last_update)) + $last_update = 'now -30 days'; - foreach ($mentioned AS $mention) { - $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", - intval($owner["uid"]), - dbesc(normalise_link($mention))); - if ($r[0]["forum"] OR $r[0]["prv"]) - xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => ACTIVITY_OBJ_GROUP, - "href" => $mention)); - else - xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => ACTIVITY_OBJ_PERSON, - "href" => $mention)); + $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); + + $items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item` + INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` + LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid` + WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted` + AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' + AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`)) + OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`) + AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`)) + OR (`item`.`author-link` IN ('%s', '%s'))) + ORDER BY `item`.`received` DESC + LIMIT 0, 300", + intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN), + //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), + //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), + dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), + dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), + dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])), + dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])) + ); + + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + + $root = self::add_header($doc, $owner); + + foreach ($items AS $item) { + $entry = self::entry($doc, $item, $owner); + $root->appendChild($entry); + } + + return(trim($doc->saveXML())); } - if (!$item["private"]) - xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection", - "href" => "http://activityschema.org/collection/public")); + public static function salmon($item,$owner) { - if(count($tags)) - foreach($tags as $t) - if ($t[0] != "@") - xml_add_element($doc, $entry, "category", "", array("term" => $t[2])); + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; - ostatus_get_attachment($doc, $entry, $item); + $entry = self::entry($doc, $item, $owner, true); - /// @TODO - /// The API call has yet to be implemented - //$attributes = array("href" => $a->get_baseurl()."/api/statuses/show/".$item["id"].".atom", - // "rel" => "self", "type" => "application/atom+xml"); - //xml_add_element($doc, $entry, "link", "", $attributes); + $doc->appendChild($entry); - //$attributes = array("href" => $a->get_baseurl()."/api/statuses/show/".$item["id"].".atom", - // "rel" => "edit", "type" => "application/atom+xml"); - //xml_add_element($doc, $entry, "link", "", $attributes); - - $app = $item["app"]; - if ($app == "") - $app = "web"; - - - $attributes = array("local_id" => $item["id"], "source" => $app); - if ($is_repeat) - $attributes["repeat_of"] = $repeated_item["id"]; - - xml_add_element($doc, $entry, "statusnet:notice_info", "", $attributes); - - return $entry; -} - -function ostatus_feed(&$a, $owner_nick, $last_update) { - - $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` - FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` - WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1", - dbesc($owner_nick)); - if (!$r) - return; - - $owner = $r[0]; - - if(!strlen($last_update)) - $last_update = 'now -30 days'; - - $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); - - $items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item` - INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` - LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid` - WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted` - AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`)) - OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`) - AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`)) - OR (`item`.`author-link` IN ('%s', '%s'))) - ORDER BY `item`.`received` DESC - LIMIT 0, 300", - intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN), - //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), - //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), - dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), - dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), - dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])), - dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])) - ); - - $doc = new DOMDocument('1.0', 'utf-8'); - $doc->formatOutput = true; - - $root = ostatus_add_header($doc, $owner); - - foreach ($items AS $item) { - $entry = ostatus_entry($doc, $item, $owner); - $root->appendChild($entry); + return(trim($doc->saveXML())); } - - return(trim($doc->saveXML())); -} - -function ostatus_salmon($item,$owner) { - - $doc = new DOMDocument('1.0', 'utf-8'); - $doc->formatOutput = true; - - $entry = ostatus_entry($doc, $item, $owner, true); - - $doc->appendChild($entry); - - return(trim($doc->saveXML())); } ?> diff --git a/include/xml.php b/include/xml.php index a454e61566..76ad88cf48 100644 --- a/include/xml.php +++ b/include/xml.php @@ -92,5 +92,40 @@ class xml { self::copy($childentry, $child, $childfield); } } + + /** + * @brief Create an XML element + * + * @param object $doc XML root + * @param string $element XML element name + * @param string $value XML value + * @param array $attributes array containing the attributes + * + * @return object XML element object + */ + public static function create_element($doc, $element, $value = "", $attributes = array()) { + $element = $doc->createElement($element, xmlify($value)); + + foreach ($attributes AS $key => $value) { + $attribute = $doc->createAttribute($key); + $attribute->value = xmlify($value); + $element->appendChild($attribute); + } + return $element; + } + + /** + * @brief Create an XML and append it to the parent object + * + * @param object $doc XML root + * @param object $parent parent object + * @param string $element XML element name + * @param string $value XML value + * @param array $attributes array containing the attributes + */ + public static function add_element($doc, $parent, $element, $value = "", $attributes = array()) { + $element = self::create_element($doc, $element, $value, $attributes); + $parent->appendChild($element); + } } ?> From 1f2c495cd8f68a9539dea7307fcbb1de3e84934b Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 30 Mar 2016 23:26:37 +0200 Subject: [PATCH 242/273] Tempory file is deleted --- include/ostatus2.php | 1778 ------------------------------------------ 1 file changed, 1778 deletions(-) delete mode 100644 include/ostatus2.php diff --git a/include/ostatus2.php b/include/ostatus2.php deleted file mode 100644 index 53c279f254..0000000000 --- a/include/ostatus2.php +++ /dev/null @@ -1,1778 +0,0 @@ -<?php -require_once("include/Contact.php"); -require_once("include/threads.php"); -require_once("include/html2bbcode.php"); -require_once("include/bbcode.php"); -require_once("include/items.php"); -require_once("mod/share.php"); -require_once("include/enotify.php"); -require_once("include/socgraph.php"); -require_once("include/Photo.php"); -require_once("include/Scrape.php"); -require_once("include/follow.php"); -require_once("include/api.php"); -require_once("mod/proxy.php"); - -class xml2 { - public static function create_element($doc, $element, $value = "", $attributes = array()) { - $element = $doc->createElement($element, xmlify($value)); - - foreach ($attributes AS $key => $value) { - $attribute = $doc->createAttribute($key); - $attribute->value = xmlify($value); - $element->appendChild($attribute); - } - return $element; - } - - public static function add_element($doc, $parent, $element, $value = "", $attributes = array()) { - $element = self::create_element($doc, $element, $value, $attributes); - $parent->appendChild($element); - } -} - -class ostatus { - const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes - const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes - const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes - - private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) { - - $author = array(); - $author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; - $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue; - - // Preserve the value - $authorlink = $author["author-link"]; - - $alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes; - if (is_object($alternate)) - foreach($alternate AS $attributes) - if ($attributes->name == "href") - $author["author-link"] = $attributes->textContent; - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", - intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), - dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET)); - if ($r) { - $contact = $r[0]; - $author["contact-id"] = $r[0]["id"]; - } else - $author["contact-id"] = $contact["id"]; - - $avatarlist = array(); - $avatars = $xpath->query("atom:author/atom:link[@rel='avatar']", $context); - foreach($avatars AS $avatar) { - $href = ""; - $width = 0; - foreach($avatar->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "width") - $width = $attributes->textContent; - } - if (($width > 0) AND ($href != "")) - $avatarlist[$width] = $href; - } - if (count($avatarlist) > 0) { - krsort($avatarlist); - $author["author-avatar"] = current($avatarlist); - } - - $displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($displayname != "") - $author["author-name"] = $displayname; - - $author["owner-name"] = $author["author-name"]; - $author["owner-link"] = $author["author-link"]; - $author["owner-avatar"] = $author["author-avatar"]; - - // Only update the contacts if it is an OStatus contact - if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { - // Update contact data - - $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; - if ($value != "") - $contact["notify"] = $value; - - $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["alias"] = $value; - - $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["name"] = $value; - - $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["nick"] = $value; - - $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["about"] = html2bbcode($value); - - $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["location"] = $value; - - if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { - - logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - - q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", - dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), - dbesc(datetime_convert()), intval($contact["id"])); - - poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], - "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); - } - - if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) { - logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); - - update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); - } - - $contact["generation"] = 2; - $contact["photo"] = $author["author-avatar"]; - update_gcontact($contact); - } - - return($author); - } - - public static function salmon_author($xml, $importer) { - - if ($xml == "") - return; - - $doc = new DOMDocument(); - @$doc->loadXML($xml); - - $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', NAMESPACE_ATOM1); - $xpath->registerNamespace('thr', NAMESPACE_THREAD); - $xpath->registerNamespace('georss', NAMESPACE_GEORSS); - $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); - $xpath->registerNamespace('media', NAMESPACE_MEDIA); - $xpath->registerNamespace('poco', NAMESPACE_POCO); - $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); - $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); - - $entries = $xpath->query('/atom:entry'); - - foreach ($entries AS $entry) { - // fetch the author - $author = self::fetchauthor($xpath, $entry, $importer, $contact, true); - return $author; - } - } - - public static function import($xml,$importer,&$contact, &$hub) { - - logger("Import OStatus message", LOGGER_DEBUG); - - if ($xml == "") - return; - - //$tempfile = tempnam(get_temppath(), "import"); - //file_put_contents($tempfile, $xml); - - $doc = new DOMDocument(); - @$doc->loadXML($xml); - - $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', NAMESPACE_ATOM1); - $xpath->registerNamespace('thr', NAMESPACE_THREAD); - $xpath->registerNamespace('georss', NAMESPACE_GEORSS); - $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); - $xpath->registerNamespace('media', NAMESPACE_MEDIA); - $xpath->registerNamespace('poco', NAMESPACE_POCO); - $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); - $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); - - $gub = ""; - $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes; - if (is_object($hub_attributes)) - foreach($hub_attributes AS $hub_attribute) - if ($hub_attribute->name == "href") { - $hub = $hub_attribute->textContent; - logger("Found hub ".$hub, LOGGER_DEBUG); - } - - $header = array(); - $header["uid"] = $importer["uid"]; - $header["network"] = NETWORK_OSTATUS; - $header["type"] = "remote"; - $header["wall"] = 0; - $header["origin"] = 0; - $header["gravity"] = GRAVITY_PARENT; - - // it could either be a received post or a post we fetched by ourselves - // depending on that, the first node is different - $first_child = $doc->firstChild->tagName; - - if ($first_child == "feed") - $entries = $xpath->query('/atom:feed/atom:entry'); - else - $entries = $xpath->query('/atom:entry'); - - $conversation = ""; - $conversationlist = array(); - $item_id = 0; - - // Reverse the order of the entries - $entrylist = array(); - - foreach ($entries AS $entry) - $entrylist[] = $entry; - - foreach (array_reverse($entrylist) AS $entry) { - - $mention = false; - - // fetch the author - if ($first_child == "feed") - $author = self::fetchauthor($xpath, $doc->firstChild, $importer, $contact, false); - else - $author = self::fetchauthor($xpath, $entry, $importer, $contact, false); - - $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; - if ($value != "") - $nickname = $value; - else - $nickname = $author["author-name"]; - - $item = array_merge($header, $author); - - // Now get the item - $item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["uri"])); - if ($r) { - logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG); - continue; - } - - $item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue)); - $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue; - - if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) { - $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; - $item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue; - } elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION) - $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue; - - $item["object"] = $xml; - $item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue; - - /// @TODO - /// Delete a message - if ($item["verb"] == "qvitter-delete-notice") { - // ignore "Delete" messages (by now) - logger("Ignore delete message ".print_r($item, true)); - continue; - } - - if ($item["verb"] == ACTIVITY_JOIN) { - // ignore "Join" messages - logger("Ignore join message ".print_r($item, true)); - continue; - } - - if ($item["verb"] == ACTIVITY_FOLLOW) { - new_follower($importer, $contact, $item, $nickname); - continue; - } - - if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") { - lose_follower($importer, $contact, $item, $dummy); - continue; - } - - if ($item["verb"] == ACTIVITY_FAVORITE) { - $orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue; - logger("Favorite ".$orig_uri." ".print_r($item, true)); - - $item["verb"] = ACTIVITY_LIKE; - $item["parent-uri"] = $orig_uri; - $item["gravity"] = GRAVITY_LIKE; - } - - if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") { - // Ignore "Unfavorite" message - logger("Ignore unfavorite message ".print_r($item, true)); - continue; - } - - // http://activitystrea.ms/schema/1.0/rsvp-yes - if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE))) - logger("Unhandled verb ".$item["verb"]." ".print_r($item, true)); - - $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue; - $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue; - $conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue; - - $related = ""; - - $inreplyto = $xpath->query('thr:in-reply-to', $entry); - if (is_object($inreplyto->item(0))) { - foreach($inreplyto->item(0)->attributes AS $attributes) { - if ($attributes->name == "ref") - $item["parent-uri"] = $attributes->textContent; - if ($attributes->name == "href") - $related = $attributes->textContent; - } - } - - $georsspoint = $xpath->query('georss:point', $entry); - if ($georsspoint) - $item["coord"] = $georsspoint->item(0)->nodeValue; - - $categories = $xpath->query('atom:category', $entry); - if ($categories) { - foreach ($categories AS $category) { - foreach($category->attributes AS $attributes) - if ($attributes->name == "term") { - $term = $attributes->textContent; - if(strlen($item["tag"])) - $item["tag"] .= ','; - $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; - } - } - } - - $self = ""; - $enclosure = ""; - - $links = $xpath->query('atom:link', $entry); - if ($links) { - $rel = ""; - $href = ""; - $type = ""; - $length = "0"; - $title = ""; - foreach ($links AS $link) { - foreach($link->attributes AS $attributes) { - if ($attributes->name == "href") - $href = $attributes->textContent; - if ($attributes->name == "rel") - $rel = $attributes->textContent; - if ($attributes->name == "type") - $type = $attributes->textContent; - if ($attributes->name == "length") - $length = $attributes->textContent; - if ($attributes->name == "title") - $title = $attributes->textContent; - } - if (($rel != "") AND ($href != "")) - switch($rel) { - case "alternate": - $item["plink"] = $href; - if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR - ($item["object-type"] == ACTIVITY_OBJ_EVENT)) - $item["body"] .= add_page_info($href); - break; - case "ostatus:conversation": - $conversation = $href; - break; - case "enclosure": - $enclosure = $href; - if(strlen($item["attach"])) - $item["attach"] .= ','; - - $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; - break; - case "related": - if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) { - if (!isset($item["parent-uri"])) - $item["parent-uri"] = $href; - - if ($related == "") - $related = $href; - } else - $item["body"] .= add_page_info($href); - break; - case "self": - $self = $href; - break; - case "mentioned": - // Notification check - if ($importer["nurl"] == normalise_link($href)) - $mention = true; - break; - } - } - } - - $local_id = ""; - $repeat_of = ""; - - $notice_info = $xpath->query('statusnet:notice_info', $entry); - if ($notice_info AND ($notice_info->length > 0)) { - foreach($notice_info->item(0)->attributes AS $attributes) { - if ($attributes->name == "source") - $item["app"] = strip_tags($attributes->textContent); - if ($attributes->name == "local_id") - $local_id = $attributes->textContent; - if ($attributes->name == "repeat_of") - $repeat_of = $attributes->textContent; - } - } - - // Is it a repeated post? - if ($repeat_of != "") { - $activityobjects = $xpath->query('activity:object', $entry)->item(0); - - if (is_object($activityobjects)) { - - $orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue; - if (!isset($orig_uri)) - $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue; - - $orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects); - if ($orig_links AND ($orig_links->length > 0)) - foreach($orig_links->item(0)->attributes AS $attributes) - if ($attributes->name == "href") - $orig_link = $attributes->textContent; - - if (!isset($orig_link)) - $orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue; - - if (!isset($orig_link)) - $orig_link = self::convert_href($orig_uri); - - $orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue; - if (!isset($orig_body)) - $orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue; - - $orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue; - - $orig_contact = $contact; - $orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false); - - $item["author-name"] = $orig_author["author-name"]; - $item["author-link"] = $orig_author["author-link"]; - $item["author-avatar"] = $orig_author["author-avatar"]; - $item["body"] = add_page_info_to_body(html2bbcode($orig_body)); - $item["created"] = $orig_created; - - $item["uri"] = $orig_uri; - $item["plink"] = $orig_link; - - $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue; - - $item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue; - if (!isset($item["object-type"])) - $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue; - } - } - - //if ($enclosure != "") - // $item["body"] .= add_page_info($enclosure); - - if (isset($item["parent-uri"])) { - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["parent-uri"])); - - if (!$r AND ($related != "")) { - $reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom"; - - if ($reply_path != $related) { - logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG); - $reply_xml = fetch_url($reply_path); - - $reply_contact = $contact; - self::import($reply_xml,$importer,$reply_contact, $reply_hub); - - // After the import try to fetch the parent item again - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($importer["uid"]), dbesc($item["parent-uri"])); - } - } - if ($r) { - $item["type"] = 'remote-comment'; - $item["gravity"] = GRAVITY_COMMENT; - } - } else - $item["parent-uri"] = $item["uri"]; - - $item_id = self::completion($conversation, $importer["uid"], $item, $self); - - if (!$item_id) { - logger("Error storing item", LOGGER_DEBUG); - continue; - } - - logger("Item was stored with id ".$item_id, LOGGER_DEBUG); - } - } - - public static function convert_href($href) { - $elements = explode(":",$href); - - if ((count($elements) <= 2) OR ($elements[0] != "tag")) - return $href; - - $server = explode(",", $elements[1]); - $conversation = explode("=", $elements[2]); - - if ((count($elements) == 4) AND ($elements[2] == "post")) - return "http://".$server[0]."/notice/".$elements[3]; - - if ((count($conversation) != 2) OR ($conversation[1] =="")) - return $href; - - if ($elements[3] == "objectType=thread") - return "http://".$server[0]."/conversation/".$conversation[1]; - else - return "http://".$server[0]."/notice/".$conversation[1]; - - return $href; - } - - public static function check_conversations($mentions = false, $override = false) { - $last = get_config('system','ostatus_last_poll'); - - $poll_interval = intval(get_config('system','ostatus_poll_interval')); - if(! $poll_interval) - $poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL; - - // Don't poll if the interval is set negative - if (($poll_interval < 0) AND !$override) - return; - - if (!$mentions) { - $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); - if (!$poll_timeframe) - $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME; - } else { - $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe')); - if (!$poll_timeframe) - $poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS; - } - - - if ($last AND !$override) { - $next = $last + ($poll_interval * 60); - if ($next > time()) { - logger('poll interval not reached'); - return; - } - } - - logger('cron_start'); - - $start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60)); - - if ($mentions) - $conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term` - STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid` - WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention` - GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start)); - else - $conversations = q("SELECT `oid`, `url`, `uid` FROM `term` - WHERE `type` = 7 AND `term` > '%s' - GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start)); - - foreach ($conversations AS $conversation) { - self::completion($conversation['url'], $conversation['uid']); - } - - logger('cron_end'); - - set_config('system','ostatus_last_poll', time()); - } - - /** - * @brief Updates the gcontact table with actor data from the conversation - * - * @param object $actor The actor object that contains the contact data - */ - private function conv_fetch_actor($actor) { - - // We set the generation to "3" since the data here is not as reliable as the data we get on other occasions - $contact = array("network" => NETWORK_OSTATUS, "generation" => 3); - - if (isset($actor->url)) - $contact["url"] = $actor->url; - - if (isset($actor->displayName)) - $contact["name"] = $actor->displayName; - - if (isset($actor->portablecontacts_net->displayName)) - $contact["name"] = $actor->portablecontacts_net->displayName; - - if (isset($actor->portablecontacts_net->preferredUsername)) - $contact["nick"] = $actor->portablecontacts_net->preferredUsername; - - if (isset($actor->id)) - $contact["alias"] = $actor->id; - - if (isset($actor->summary)) - $contact["about"] = $actor->summary; - - if (isset($actor->portablecontacts_net->note)) - $contact["about"] = $actor->portablecontacts_net->note; - - if (isset($actor->portablecontacts_net->addresses->formatted)) - $contact["location"] = $actor->portablecontacts_net->addresses->formatted; - - - if (isset($actor->image->url)) - $contact["photo"] = $actor->image->url; - - if (isset($actor->image->width)) - $avatarwidth = $actor->image->width; - - if (is_array($actor->status_net->avatarLinks)) - foreach ($actor->status_net->avatarLinks AS $avatar) { - if ($avatarsize < $avatar->width) { - $contact["photo"] = $avatar->url; - $avatarsize = $avatar->width; - } - } - - update_gcontact($contact); - } - - /** - * @brief Fetches the conversation url for a given item link or conversation id - * - * @param string $self The link to the posting - * @param string $conversation_id The conversation id - * - * @return string The conversation url - */ - private function fetch_conversation($self, $conversation_id = "") { - - if ($conversation_id != "") { - $elements = explode(":", $conversation_id); - - if ((count($elements) <= 2) OR ($elements[0] != "tag")) - return $conversation_id; - } - - if ($self == "") - return ""; - - $json = str_replace(".atom", ".json", $self); - - $raw = fetch_url($json); - if ($raw == "") - return ""; - - $data = json_decode($raw); - if (!is_object($data)) - return ""; - - $conversation_id = $data->statusnet_conversation_id; - - $pos = strpos($self, "/api/statuses/show/"); - $base_url = substr($self, 0, $pos); - - return $base_url."/conversation/".$conversation_id; - } - - /** - * @brief Fetches actor details of a given actor and user id - * - * @param string $actor The actor url - * @param int $uid The user id - * @param int $contact_id The default contact-id - * - * @return array Array with actor details - */ - private function get_actor_details($actor, $uid, $contact_id) { - - $details = array(); - - $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", - $uid, normalise_link($actor), NETWORK_STATUSNET); - - if (!$contact) - $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'", - $uid, $actor, normalise_link($actor), NETWORK_STATUSNET); - - if ($contact) { - logger("Found contact for url ".$actor, LOGGER_DEBUG); - $details["contact_id"] = $contact[0]["id"]; - $details["network"] = $contact[0]["network"]; - - $details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND)); - } else { - logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG); - - // Adding a global contact - /// @TODO Use this data for the post - $details["global_contact_id"] = get_contact($actor, 0); - - logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG); - - $details["contact_id"] = $contact_id; - $details["network"] = NETWORK_OSTATUS; - - $details["not_following"] = true; - } - - return $details; - } - - private function completion($conversation_url, $uid, $item = array(), $self = "") { - - - $item_stored = -1; - - $conversation_url = self::fetch_conversation($self, $conversation_url); - - // If the thread shouldn't be completed then store the item and go away - // Don't do a completion on liked content - if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR - ($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) { - $item_stored = item_store($item, true); - return($item_stored); - } - - // Get the parent - $parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN - (SELECT `parent` FROM `item` WHERE `id` IN - (SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))", - intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url)); - - if ($parents) - $parent = $parents[0]; - elseif (count($item) > 0) { - $parent = $item; - $parent["type"] = "remote"; - $parent["verb"] = ACTIVITY_POST; - $parent["visible"] = 1; - } else { - // Preset the parent - $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid); - if (!$r) - return(-2); - - $parent = array(); - $parent["id"] = 0; - $parent["parent"] = 0; - $parent["uri"] = ""; - $parent["contact-id"] = $r[0]["id"]; - $parent["type"] = "remote"; - $parent["verb"] = ACTIVITY_POST; - $parent["visible"] = 1; - } - - $conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as"; - $pageno = 1; - $items = array(); - - logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid); - - do { - $conv_arr = z_fetch_url($conv."?page=".$pageno); - - // If it is a non-ssl site and there is an error, then try ssl or vice versa - if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) { - $conv = str_replace("http://", "https://", $conv); - $conv_as = fetch_url($conv."?page=".$pageno); - } elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) { - $conv = str_replace("https://", "http://", $conv); - $conv_as = fetch_url($conv."?page=".$pageno); - } else - $conv_as = $conv_arr["body"]; - - $conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as); - $conv_as = json_decode($conv_as); - - $no_of_items = sizeof($items); - - if (@is_array($conv_as->items)) - foreach ($conv_as->items AS $single_item) - $items[$single_item->id] = $single_item; - - if ($no_of_items == sizeof($items)) - break; - - $pageno++; - - } while (true); - - logger('fetching conversation done. Found '.count($items).' items'); - - if (!sizeof($items)) { - if (count($item) > 0) { - $item_stored = item_store($item, true); - - if ($item_stored) { - logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG); - self::store_conversation($item_id, $conversation_url); - } - - return($item_stored); - } else - return(-3); - } - - $items = array_reverse($items); - - $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid)); - $importer = $r[0]; - - $new_parent = true; - - foreach ($items as $single_conv) { - - // Update the gcontact table - self::conv_fetch_actor($single_conv->actor); - - // Test - remove before flight - //$tempfile = tempnam(get_temppath(), "conversation"); - //file_put_contents($tempfile, json_encode($single_conv)); - - $mention = false; - - if (isset($single_conv->object->id)) - $single_conv->id = $single_conv->object->id; - - $plink = self::convert_href($single_conv->id); - if (isset($single_conv->object->url)) - $plink = self::convert_href($single_conv->object->url); - - if (@!$single_conv->id) - continue; - - logger("Got id ".$single_conv->id, LOGGER_DEBUG); - - if ($first_id == "") { - $first_id = $single_conv->id; - - // The first post of the conversation isn't our first post. There are three options: - // 1. Our conversation hasn't the "real" thread starter - // 2. This first post is a post inside our thread - // 3. This first post is a post inside another thread - if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) { - - $new_parent = true; - - $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN - (SELECT `parent` FROM `item` - WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1", - intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($new_parents) { - if ($new_parents[0]["parent"] == $parent["parent"]) { - // Option 2: This post is already present inside our thread - but not as thread starter - logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG); - $first_id = $parent["uri"]; - } else { - // Option 3: Not so good. We have mixed parents. We have to see how to clean this up. - // For now just take the new parent. - $parent = $new_parents[0]; - $first_id = $parent["uri"]; - logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG); - } - } else { - // Option 1: We hadn't got the real thread starter - // We have to clean up our existing messages. - $parent["id"] = 0; - $parent["uri"] = $first_id; - logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG); - } - } elseif ($parent["uri"] == "") { - $parent["id"] = 0; - $parent["uri"] = $first_id; - } - } - - $parent_uri = $parent["uri"]; - - // "context" only seems to exist on older servers - if (isset($single_conv->context->inReplyTo->id)) { - $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", - intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($parent_exists) - $parent_uri = $single_conv->context->inReplyTo->id; - } - - // This is the current way - if (isset($single_conv->object->inReplyTo->id)) { - $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", - intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($parent_exists) - $parent_uri = $single_conv->object->inReplyTo->id; - } - - $message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1", - intval($uid), dbesc($single_conv->id), - dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN)); - if ($message_exists) { - logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG); - - if ($parent["id"] != 0) { - $existing_message = $message_exists[0]; - - // We improved the way we fetch OStatus messages, this shouldn't happen very often now - /// @TODO We have to change the shadow copies as well. This way here is really ugly. - if ($existing_message["parent"] != $parent["id"]) { - logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG); - - // Update the parent id of the selected item - $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d", - intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"])); - - // Update the parent uri in the thread - but only if it points to itself - $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`", - dbesc($parent_uri), intval($existing_message["id"])); - - // try to change all items of the same parent - $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d", - intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"])); - - // Update the parent uri in the thread - but only if it points to itself - $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)", - dbesc($parent["uri"]), intval($existing_message["parent"])); - - // Now delete the thread - delete_thread($existing_message["parent"]); - } - } - - // The item we are having on the system is the one that we wanted to store via the item array - if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) { - $item = array(); - $item_stored = 0; - } - - continue; - } - - if (is_array($single_conv->to)) - foreach($single_conv->to AS $to) - if ($importer["nurl"] == normalise_link($to->id)) - $mention = true; - - $actor = $single_conv->actor->id; - if (isset($single_conv->actor->url)) - $actor = $single_conv->actor->url; - - $details = self::get_actor_details($actor, $uid, $parent["contact-id"]); - - // Do we only want to import threads that were started by our contacts? - if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) { - logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG); - continue; - } - - $arr = array(); - $arr["network"] = $details["network"]; - $arr["uri"] = $single_conv->id; - $arr["plink"] = $plink; - $arr["uid"] = $uid; - $arr["contact-id"] = $details["contact_id"]; - $arr["parent-uri"] = $parent_uri; - $arr["created"] = $single_conv->published; - $arr["edited"] = $single_conv->published; - $arr["owner-name"] = $single_conv->actor->displayName; - if ($arr["owner-name"] == '') - $arr["owner-name"] = $single_conv->actor->contact->displayName; - if ($arr["owner-name"] == '') - $arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName; - - $arr["owner-link"] = $actor; - $arr["owner-avatar"] = $single_conv->actor->image->url; - $arr["author-name"] = $arr["owner-name"]; - $arr["author-link"] = $actor; - $arr["author-avatar"] = $single_conv->actor->image->url; - $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content)); - - if (isset($single_conv->status_net->notice_info->source)) - $arr["app"] = strip_tags($single_conv->status_net->notice_info->source); - elseif (isset($single_conv->statusnet->notice_info->source)) - $arr["app"] = strip_tags($single_conv->statusnet->notice_info->source); - elseif (isset($single_conv->statusnet_notice_info->source)) - $arr["app"] = strip_tags($single_conv->statusnet_notice_info->source); - elseif (isset($single_conv->provider->displayName)) - $arr["app"] = $single_conv->provider->displayName; - else - $arr["app"] = "OStatus"; - - - $arr["object"] = json_encode($single_conv); - $arr["verb"] = $parent["verb"]; - $arr["visible"] = $parent["visible"]; - $arr["location"] = $single_conv->location->displayName; - $arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon); - - // Is it a reshared item? - if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) { - if (is_array($single_conv->object)) - $single_conv->object = $single_conv->object[0]; - - logger("Found reshared item ".$single_conv->object->id); - - // $single_conv->object->context->conversation; - - if (isset($single_conv->object->object->id)) - $arr["uri"] = $single_conv->object->object->id; - else - $arr["uri"] = $single_conv->object->id; - - if (isset($single_conv->object->object->url)) - $plink = self::convert_href($single_conv->object->object->url); - else - $plink = self::convert_href($single_conv->object->url); - - if (isset($single_conv->object->object->content)) - $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content)); - else - $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content)); - - $arr["plink"] = $plink; - - $arr["created"] = $single_conv->object->published; - $arr["edited"] = $single_conv->object->published; - - $arr["author-name"] = $single_conv->object->actor->displayName; - if ($arr["owner-name"] == '') - $arr["author-name"] = $single_conv->object->actor->contact->displayName; - - $arr["author-link"] = $single_conv->object->actor->url; - $arr["author-avatar"] = $single_conv->object->actor->image->url; - - $arr["app"] = $single_conv->object->provider->displayName."#"; - //$arr["verb"] = $single_conv->object->verb; - - $arr["location"] = $single_conv->object->location->displayName; - $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon); - } - - if ($arr["location"] == "") - unset($arr["location"]); - - if ($arr["coord"] == "") - unset($arr["coord"]); - - // Copy fields from given item array - if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] == $single_conv->id))) { - $copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar", - "gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag", - "title", "attach", "app", "type", "location", "contact-id", "uri"); - foreach ($copy_fields AS $field) - if (isset($item[$field])) - $arr[$field] = $item[$field]; - - } - - $newitem = item_store($arr); - if (!$newitem) { - logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG); - continue; - } - - if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) { - $item = array(); - $item_stored = $newitem; - } - - logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG); - - // Add the conversation entry (but don't fetch the whole conversation) - self::store_conversation($newitem, $conversation_url); - - // If the newly created item is the top item then change the parent settings of the thread - // This shouldn't happen anymore. This is supposed to be absolote. - if ($arr["uri"] == $first_id) { - logger('setting new parent to id '.$newitem); - $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", - intval($uid), intval($newitem)); - if ($new_parents) - $parent = $new_parents[0]; - } - } - - if (($item_stored < 0) AND (count($item) > 0)) { - - if (get_config('system','ostatus_full_threads')) { - $details = self::get_actor_details($item["owner-link"], $uid, $item["contact-id"]); - if ($details["not_following"]) { - logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG); - return false; - } - } - - $item_stored = item_store($item, true); - if ($item_stored) { - logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG); - self::store_conversation($item_stored, $conversation_url); - } - } - - return($item_stored); - } - - private function store_conversation($itemid, $conversation_url) { - - $conversation_url = self::convert_href($conversation_url); - - $messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid)); - if (!$messages) - return; - $message = $messages[0]; - - // Store conversation url if not done before - $conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d", - intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION)); - - if (!$conversation) { - $r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')", - intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), - dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"])); - logger('Storing conversation url '.$conversation_url.' for id '.$itemid); - } - } - - private function get_reshared_guid($item) { - $body = trim($item["body"]); - - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(""); - - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(""); - - $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); - // Skip if there is no shared message in there - if ($body == $attributes) - return(false); - - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - return $guid; - } - - private function format_picture_post($body) { - $siteinfo = get_attached_data($body); - - if (($siteinfo["type"] == "photo")) { - if (isset($siteinfo["preview"])) - $preview = $siteinfo["preview"]; - else - $preview = $siteinfo["image"]; - - // Is it a remote picture? Then make a smaller preview here - $preview = proxy_url($preview, false, PROXY_SIZE_SMALL); - - // Is it a local picture? Then make it smaller here - $preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview); - $preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview); - - if (isset($siteinfo["url"])) - $url = $siteinfo["url"]; - else - $url = $siteinfo["image"]; - - $body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]"; - } - - return $body; - } - - private function add_header($doc, $owner) { - - $a = get_app(); - - $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); - $doc->appendChild($root); - - $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); - $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); - $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); - $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); - $root->setAttribute("xmlns:poco", NAMESPACE_POCO); - $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); - $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); - - $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); - xml2::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); - xml2::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]); - xml2::add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"])); - xml2::add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"])); - xml2::add_element($doc, $root, "logo", $owner["photo"]); - xml2::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); - - $author = self::add_author($doc, $owner); - $root->appendChild($author); - - $attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html"); - xml2::add_element($doc, $root, "link", "", $attributes); - - /// @TODO We have to find out what this is - /// $attributes = array("href" => App::get_baseurl()."/sup", - /// "rel" => "http://api.friendfeed.com/2008/03#sup", - /// "type" => "application/json"); - /// xml2::add_element($doc, $root, "link", "", $attributes); - - self::hublinks($doc, $root); - - $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon"); - xml2::add_element($doc, $root, "link", "", $attributes); - - $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies"); - xml2::add_element($doc, $root, "link", "", $attributes); - - $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention"); - xml2::add_element($doc, $root, "link", "", $attributes); - - $attributes = array("href" => App::get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom", - "rel" => "self", "type" => "application/atom+xml"); - xml2::add_element($doc, $root, "link", "", $attributes); - - return $root; - } - - public static function hublinks($doc, $root) { - $hub = get_config('system','huburl'); - - $hubxml = ''; - if(strlen($hub)) { - $hubs = explode(',', $hub); - if(count($hubs)) { - foreach($hubs as $h) { - $h = trim($h); - if(! strlen($h)) - continue; - if ($h === '[internal]') - $h = App::get_baseurl() . '/pubsubhubbub'; - xml2::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub")); - } - } - } - } - - private function get_attachment($doc, $root, $item) { - $o = ""; - $siteinfo = get_attached_data($item["body"]); - - switch($siteinfo["type"]) { - case 'link': - $attributes = array("rel" => "enclosure", - "href" => $siteinfo["url"], - "type" => "text/html; charset=UTF-8", - "length" => "", - "title" => $siteinfo["title"]); - xml2::add_element($doc, $root, "link", "", $attributes); - break; - case 'photo': - $imgdata = get_photo_info($siteinfo["image"]); - $attributes = array("rel" => "enclosure", - "href" => $siteinfo["image"], - "type" => $imgdata["mime"], - "length" => intval($imgdata["size"])); - xml2::add_element($doc, $root, "link", "", $attributes); - break; - case 'video': - $attributes = array("rel" => "enclosure", - "href" => $siteinfo["url"], - "type" => "text/html; charset=UTF-8", - "length" => "", - "title" => $siteinfo["title"]); - xml2::add_element($doc, $root, "link", "", $attributes); - break; - default: - break; - } - - if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) { - $photodata = get_photo_info($siteinfo["image"]); - - $attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]); - xml2::add_element($doc, $root, "link", "", $attributes); - } - - - $arr = explode('[/attach],',$item['attach']); - if(count($arr)) { - foreach($arr as $r) { - $matches = false; - $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches); - if($cnt) { - $attributes = array("rel" => "enclosure", - "href" => $matches[1], - "type" => $matches[3]); - - if(intval($matches[2])) - $attributes["length"] = intval($matches[2]); - - if(trim($matches[4]) != "") - $attributes["title"] = trim($matches[4]); - - xml2::add_element($doc, $root, "link", "", $attributes); - } - } - } - } - - private function add_author($doc, $owner) { - - $r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"])); - if ($r) - $profile = $r[0]; - - $author = $doc->createElement("author"); - xml2::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON); - xml2::add_element($doc, $author, "uri", $owner["url"]); - xml2::add_element($doc, $author, "name", $owner["name"]); - xml2::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7)); - - $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]); - xml2::add_element($doc, $author, "link", "", $attributes); - - $attributes = array( - "rel" => "avatar", - "type" => "image/jpeg", // To-Do? - "media:width" => 175, - "media:height" => 175, - "href" => $owner["photo"]); - xml2::add_element($doc, $author, "link", "", $attributes); - - if (isset($owner["thumb"])) { - $attributes = array( - "rel" => "avatar", - "type" => "image/jpeg", // To-Do? - "media:width" => 80, - "media:height" => 80, - "href" => $owner["thumb"]); - xml2::add_element($doc, $author, "link", "", $attributes); - } - - xml2::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]); - xml2::add_element($doc, $author, "poco:displayName", $owner["name"]); - xml2::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7)); - - if (trim($owner["location"]) != "") { - $element = $doc->createElement("poco:address"); - xml2::add_element($doc, $element, "poco:formatted", $owner["location"]); - $author->appendChild($element); - } - - if (trim($profile["homepage"]) != "") { - $urls = $doc->createElement("poco:urls"); - xml2::add_element($doc, $urls, "poco:type", "homepage"); - xml2::add_element($doc, $urls, "poco:value", $profile["homepage"]); - xml2::add_element($doc, $urls, "poco:primary", "true"); - $author->appendChild($urls); - } - - if (count($profile)) { - xml2::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"])); - xml2::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"])); - } - - return $author; - } - - /** - * @TODO Picture attachments should look like this: - * <a href="https://status.pirati.ca/attachment/572819" title="https://status.pirati.ca/file/heluecht-20151202T222602-rd3u49p.gif" - * class="attachment thumbnail" id="attachment-572819" rel="nofollow external">https://status.pirati.ca/attachment/572819</a> - * - */ - - function construct_verb($item) { - if ($item['verb']) - return $item['verb']; - return ACTIVITY_POST; - } - - function construct_objecttype($item) { - if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT))) - return $item['object-type']; - return ACTIVITY_OBJ_NOTE; - } - - private function entry($doc, $item, $owner, $toplevel = false) { - $repeated_guid = self::get_reshared_guid($item); - if ($repeated_guid != "") - $xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel); - - if ($xml) - return $xml; - - if ($item["verb"] == ACTIVITY_LIKE) - return self::like_entry($doc, $item, $owner, $toplevel); - else - return self::note_entry($doc, $item, $owner, $toplevel); - } - - private function source_entry($doc, $contact) { - $source = $doc->createElement("source"); - xml2::add_element($doc, $source, "id", $contact["poll"]); - xml2::add_element($doc, $source, "title", $contact["name"]); - xml2::add_element($doc, $source, "link", "", array("rel" => "alternate", - "type" => "text/html", - "href" => $contact["alias"])); - xml2::add_element($doc, $source, "link", "", array("rel" => "self", - "type" => "application/atom+xml", - "href" => $contact["poll"])); - xml2::add_element($doc, $source, "icon", $contact["photo"]); - xml2::add_element($doc, $source, "updated", datetime_convert("UTC","UTC",$contact["success_update"]."+00:00",ATOM_TIME)); - - return $source; - } - - private function contact_entry($url, $owner) { - - $r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1", - dbesc(normalise_link($url)), intval($owner["uid"])); - if ($r) { - $contact = $r[0]; - $contact["uid"] = -1; - } - - if (!$r) { - $r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1", - dbesc(normalise_link($url))); - if ($r) { - $contact = $r[0]; - $contact["uid"] = -1; - $contact["success_update"] = $contact["updated"]; - } - } - - if (!$r) - $contact = owner; - - if (!isset($contact["poll"])) { - $data = probe_url($url); - $contact["alias"] = $data["alias"]; - $contact["poll"] = $data["poll"]; - } - - if (!isset($contact["alias"])) - $contact["alias"] = $contact["url"]; - - return $contact; - } - - private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) { - - if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { - logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); - } - - $title = self::entry_header($doc, $entry, $owner, $toplevel); - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' AND NOT `private` AND `network` IN ('%s', '%s', '%s') LIMIT 1", - intval($owner["uid"]), dbesc($repeated_guid), - dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); - if ($r) - $repeated_item = $r[0]; - else - return false; - - $contact = self::contact_entry($repeated_item['author-link'], $owner); - - $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); - - $title = $owner["nick"]." repeated a notice by ".$contact["nick"]; - - self::entry_content($doc, $entry, $item, $owner, $title, ACTIVITY_SHARE, false); - - $as_object = $doc->createElement("activity:object"); - -// ostatusWaEeYs -// ostatusogu9zg - besser - xml2::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity"); - - self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false); - - $author = self::add_author($doc, $contact); - $as_object->appendChild($author); - - $as_object2 = $doc->createElement("activity:object"); - - xml2::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item)); - - $title = sprintf("New comment by %s", $contact["nick"]); - - self::entry_content($doc, $as_object2, $repeated_item, $owner, $title); - - $as_object->appendChild($as_object2); - - self::entry_footer($doc, $as_object, $item, $owner, false); - - $source = self::source_entry($doc, $contact); - - $as_object->appendChild($source); - - $entry->appendChild($as_object); - - self::entry_footer($doc, $entry, $item, $owner); - - return $entry; - } - - private function like_entry($doc, $item, $owner, $toplevel) { - - if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { - logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); - } - - $title = self::entry_header($doc, $entry, $owner, $toplevel); - - $verb = NAMESPACE_ACTIVITY_SCHEMA."favorite"; - self::entry_content($doc, $entry, $item, $owner, "Favorite", $verb, false); - - $as_object = $doc->createElement("activity:object"); - - $parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"])); - $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); - - xml2::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0])); - - self::entry_content($doc, $as_object, $parent[0], $owner, "New entry"); - - $entry->appendChild($as_object); - - self::entry_footer($doc, $entry, $item, $owner); - - return $entry; - } - - private function note_entry($doc, $item, $owner, $toplevel) { - - if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { - logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); - } - - $title = self::entry_header($doc, $entry, $owner, $toplevel); - - xml2::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); - - self::entry_content($doc, $entry, $item, $owner, $title); - - self::entry_footer($doc, $entry, $item, $owner); - - return $entry; - } - - private function entry_header($doc, &$entry, $owner, $toplevel) { - if (!$toplevel) { - $entry = $doc->createElement("entry"); - $title = sprintf("New note by %s", $owner["nick"]); - } else { - $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry"); - - $entry->setAttribute("xmlns:thr", NAMESPACE_THREAD); - $entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS); - $entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); - $entry->setAttribute("xmlns:media", NAMESPACE_MEDIA); - $entry->setAttribute("xmlns:poco", NAMESPACE_POCO); - $entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); - $entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); - - $author = self::add_author($doc, $owner); - $entry->appendChild($author); - - $title = sprintf("New comment by %s", $owner["nick"]); - } - return $title; - } - - private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) { - - if ($verb == "") - $verb = self::construct_verb($item); - - xml2::add_element($doc, $entry, "id", $item["uri"]); - xml2::add_element($doc, $entry, "title", $title); - - $body = self::format_picture_post($item['body']); - - if ($item['title'] != "") - $body = "[b]".$item['title']."[/b]\n\n".$body; - - $body = bbcode($body, false, false, 7); - - xml2::add_element($doc, $entry, "content", $body, array("type" => "html")); - - xml2::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", - "href" => App::get_baseurl()."/display/".$item["guid"])); - - if ($complete) - xml2::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"])); - - xml2::add_element($doc, $entry, "activity:verb", $verb); - - xml2::add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); - xml2::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); - } - - private function entry_footer($doc, $entry, $item, $owner, $complete = true) { - - $mentioned = array(); - - if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { - $parent = q("SELECT `guid`, `author-link`, `owner-link` 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"]); - xml2::add_element($doc, $entry, "thr:in-reply-to", "", $attributes); - - $attributes = array( - "rel" => "related", - "href" => App::get_baseurl()."/display/".$parent[0]["guid"]); - xml2::add_element($doc, $entry, "link", "", $attributes); - - $mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"]; - $mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"]; - - $thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($owner["uid"]), - dbesc($parent_item)); - if ($thrparent) { - $mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"]; - $mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"]; - } - } - - xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation", - "href" => App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"])); - xml2::add_element($doc, $entry, "ostatus:conversation", App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]); - - $tags = item_getfeedtags($item); - - if(count($tags)) - foreach($tags as $t) - if ($t[0] == "@") - $mentioned[$t[1]] = $t[1]; - - // Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS) - $newmentions = array(); - foreach ($mentioned AS $mention) { - $newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention); - $newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention); - } - $mentioned = $newmentions; - - foreach ($mentioned AS $mention) { - $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", - intval($owner["uid"]), - dbesc(normalise_link($mention))); - if ($r[0]["forum"] OR $r[0]["prv"]) - xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => ACTIVITY_OBJ_GROUP, - "href" => $mention)); - else - xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => ACTIVITY_OBJ_PERSON, - "href" => $mention)); - } - - if (!$item["private"]) { - xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention", - "href" => "http://activityschema.org/collection/public")); - xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection", - "href" => "http://activityschema.org/collection/public")); - } - - if(count($tags)) - foreach($tags as $t) - if ($t[0] != "@") - xml2::add_element($doc, $entry, "category", "", array("term" => $t[2])); - - self::get_attachment($doc, $entry, $item); - - if ($complete) { - $app = $item["app"]; - if ($app == "") - $app = "web"; - - $attributes = array("local_id" => $item["id"], "source" => $app); - - if (isset($parent["id"])) - $attributes["repeat_of"] = $parent["id"]; - - if ($item["coord"] != "") - xml2::add_element($doc, $entry, "georss:point", $item["coord"]); - - xml2::add_element($doc, $entry, "statusnet:notice_info", "", $attributes); - } - } - - public static function feed(&$a, $owner_nick, $last_update) { - - $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` - FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` - WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1", - dbesc($owner_nick)); - if (!$r) - return; - - $owner = $r[0]; - - if(!strlen($last_update)) - $last_update = 'now -30 days'; - - $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); - - $items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item` - INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` - LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid` - WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted` - AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`)) - OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`) - AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`)) - OR (`item`.`author-link` IN ('%s', '%s'))) - ORDER BY `item`.`received` DESC - LIMIT 0, 300", - intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN), - //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), - //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS), - dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), - dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), - dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])), - dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])) - ); - - $doc = new DOMDocument('1.0', 'utf-8'); - $doc->formatOutput = true; - - $root = self::add_header($doc, $owner); - - foreach ($items AS $item) { - $entry = self::entry($doc, $item, $owner); - $root->appendChild($entry); - } - - return(trim($doc->saveXML())); - } - - public static function salmon($item,$owner) { - - $doc = new DOMDocument('1.0', 'utf-8'); - $doc->formatOutput = true; - - $entry = self::entry($doc, $item, $owner, true); - - $doc->appendChild($entry); - - return(trim($doc->saveXML())); - } -} -?> From 6ff5c23d50095e5407bf69f8a4ec2220c5d2d408 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 31 Mar 2016 00:14:51 +0200 Subject: [PATCH 243/273] Doxygen structure added --- include/ostatus.php | 240 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 2 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index ba5b80cd5d..dcbd91f415 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -1,4 +1,8 @@ <?php +/** + * @file include/ostatus.php + */ + require_once("include/Contact.php"); require_once("include/threads.php"); require_once("include/html2bbcode.php"); @@ -14,11 +18,26 @@ require_once("include/api.php"); require_once("mod/proxy.php"); require_once("include/xml.php"); +/** + * @brief This class contain functions for the OStatus protocol + * + */ class ostatus { const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes + /** + * @brief + * + * @param $xpath + * @param $context + * @param $importer + * @param $contact + * @param $onlyfetch + * + * @return + */ private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) { $author = array(); @@ -124,6 +143,14 @@ class ostatus { return($author); } + /** + * @brief + * + * @param $xml + * @param $importer + * + * @return + */ public static function salmon_author($xml, $importer) { if ($xml == "") @@ -151,6 +178,16 @@ class ostatus { } } + /** + * @brief + * + * @param $xml + * @param $importer + * @param $contact + * @param $hub + * + * @return + */ public static function import($xml,$importer,&$contact, &$hub) { logger("Import OStatus message", LOGGER_DEBUG); @@ -492,6 +529,13 @@ class ostatus { } } + /** + * @brief + * + * @param $href + * + * @return + */ public static function convert_href($href) { $elements = explode(":",$href); @@ -515,6 +559,14 @@ class ostatus { return $href; } + /** + * @brief + * + * @param $mentions + * @param $override + * + * @return + */ public static function check_conversations($mentions = false, $override = false) { $last = get_config('system','ostatus_last_poll'); @@ -702,6 +754,15 @@ class ostatus { return $details; } + /** + * @brief + * + * @param $conversation_url + * @param $uid + * @param $item + * + * @return + */ private function completion($conversation_url, $uid, $item = array(), $self = "") { @@ -1093,6 +1154,14 @@ class ostatus { return($item_stored); } + /** + * @brief + * + * @param $itemid + * @param $conversation_url + * + * @return + */ private function store_conversation($itemid, $conversation_url) { $conversation_url = self::convert_href($conversation_url); @@ -1114,6 +1183,13 @@ class ostatus { } } + /** + * @brief + * + * @param $item + * + * @return + */ private function get_reshared_guid($item) { $body = trim($item["body"]); @@ -1143,6 +1219,13 @@ class ostatus { return $guid; } + /** + * @brief + * + * @param $body + * + * @return + */ private function format_picture_post($body) { $siteinfo = get_attached_data($body); @@ -1170,6 +1253,14 @@ class ostatus { return $body; } + /** + * @brief + * + * @param $doc + * @param $owner + * + * @return + */ private function add_header($doc, $owner) { $a = get_app(); @@ -1223,6 +1314,14 @@ class ostatus { return $root; } + /** + * @brief + * + * @param $doc + * @param $root + * + * @return + */ public static function hublinks($doc, $root) { $hub = get_config('system','huburl'); @@ -1242,6 +1341,15 @@ class ostatus { } } + /** + * @brief + * + * @param $doc + * @param $root + * @param $item + * + * @return + */ private function get_attachment($doc, $root, $item) { $o = ""; $siteinfo = get_attached_data($item["body"]); @@ -1305,6 +1413,14 @@ class ostatus { } } + /** + * @brief + * + * @param $doc + * @param $owner + * + * @return + */ private function add_author($doc, $owner) { $r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"])); @@ -1371,18 +1487,42 @@ class ostatus { * */ + /** + * @brief + * + * @param $item + * + * @return + */ function construct_verb($item) { if ($item['verb']) return $item['verb']; return ACTIVITY_POST; } + /** + * @brief + * + * @param $item + * + * @return + */ function construct_objecttype($item) { if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT))) return $item['object-type']; return ACTIVITY_OBJ_NOTE; } + /** + * @brief + * + * @param $doc + * @param $item + * @param $owner + * @param $toplevel + * + * @return + */ private function entry($doc, $item, $owner, $toplevel = false) { $repeated_guid = self::get_reshared_guid($item); if ($repeated_guid != "") @@ -1397,6 +1537,14 @@ class ostatus { return self::note_entry($doc, $item, $owner, $toplevel); } + /** + * @brief + * + * @param $doc + * @param $contact + * + * @return + */ private function source_entry($doc, $contact) { $source = $doc->createElement("source"); xml::add_element($doc, $source, "id", $contact["poll"]); @@ -1413,6 +1561,14 @@ class ostatus { return $source; } + /** + * @brief + * + * @param $url + * @param $owner + * + * @return + */ private function contact_entry($url, $owner) { $r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1", @@ -1447,6 +1603,17 @@ class ostatus { return $contact; } + /** + * @brief + * + * @param $doc + * @param $item + * @param $owner + * @param $repeated_guid + * @param $toplevel + * + * @return + */ private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) { if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { @@ -1473,8 +1640,6 @@ class ostatus { $as_object = $doc->createElement("activity:object"); -// ostatusWaEeYs -// ostatusogu9zg - besser xml::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity"); self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false); @@ -1505,6 +1670,16 @@ class ostatus { return $entry; } + /** + * @brief + * + * @param $doc + * @param $item + * @param $owner + * @param $toplevel + * + * @return + */ private function like_entry($doc, $item, $owner, $toplevel) { if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { @@ -1532,6 +1707,16 @@ class ostatus { return $entry; } + /** + * @brief + * + * @param $doc + * @param $item + * @param $owner + * @param $toplevel + * + * @return + */ private function note_entry($doc, $item, $owner, $toplevel) { if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { @@ -1549,6 +1734,16 @@ class ostatus { return $entry; } + /** + * @brief + * + * @param $doc + * @param $entry + * @param $owner + * @param $toplevel + * + * @return + */ private function entry_header($doc, &$entry, $owner, $toplevel) { if (!$toplevel) { $entry = $doc->createElement("entry"); @@ -1572,6 +1767,19 @@ class ostatus { return $title; } + /** + * @brief + * + * @param $doc + * @param $entry + * @param $item + * @param $owner + * @param $title + * @param $verb + * @param $complete + * + * @return + */ private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) { if ($verb == "") @@ -1601,6 +1809,17 @@ class ostatus { xml::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); } + /** + * @brief + * + * @param $doc + * @param $entry + * @param $item + * @param $owner + * @param $complete + * + * @return + */ private function entry_footer($doc, $entry, $item, $owner, $complete = true) { $mentioned = array(); @@ -1697,6 +1916,15 @@ class ostatus { } } + /** + * @brief + * + * @param $a + * @param $owner_nick + * @param $last_update + * + * @return + */ public static function feed(&$a, $owner_nick, $last_update) { $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` @@ -1746,6 +1974,14 @@ class ostatus { return(trim($doc->saveXML())); } + /** + * @brief + * + * @param $item + * @param $owner + * + * @return + */ public static function salmon($item,$owner) { $doc = new DOMDocument('1.0', 'utf-8'); From ffd73d137bee6d17f44adc8bb37bfd15c23ec67e Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 31 Mar 2016 07:34:13 +0200 Subject: [PATCH 244/273] Added some documentation --- include/ostatus.php | 126 +++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index dcbd91f415..0796fcc3b7 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -28,15 +28,15 @@ class ostatus { const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes /** - * @brief + * @brief Fetches author data * - * @param $xpath - * @param $context - * @param $importer - * @param $contact - * @param $onlyfetch + * @param object $xpath The xpath object + * @param object $context The xml context of the author detals + * @param array $importer user record of the importing user + * @param array $contact Called by reference, will contain the fetched contact + * @param bool $onlyfetch Only fetch the header without updating the contact entries * - * @return + * @return array Array of author related entries for the item */ private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) { @@ -147,7 +147,7 @@ class ostatus { * @brief * * @param $xml - * @param $importer + * @param array $importer user record of the importing user * * @return */ @@ -179,14 +179,12 @@ class ostatus { } /** - * @brief + * @brief Imports an XML string containing OStatus elements * - * @param $xml - * @param $importer + * @param string $xml The XML + * @param array $importer user record of the importing user * @param $contact - * @param $hub - * - * @return + * @param array $hub Called by reference, returns the fetched hub data */ public static function import($xml,$importer,&$contact, &$hub) { @@ -530,11 +528,11 @@ class ostatus { } /** - * @brief + * @brief Create an url out of an uri * - * @param $href + * @param string $href URI in the format "parameter1:parameter1:..." * - * @return + * @return string URL in the format http(s)://.... */ public static function convert_href($href) { $elements = explode(":",$href); @@ -560,12 +558,10 @@ class ostatus { } /** - * @brief + * @brief Checks if there are entries in conversations that aren't present on our side * - * @param $mentions - * @param $override - * - * @return + * @param bool $mentions Fetch conversations where we are mentioned + * @param bool $override Override the interval setting */ public static function check_conversations($mentions = false, $override = false) { $last = get_config('system','ostatus_last_poll'); @@ -759,7 +755,7 @@ class ostatus { * * @param $conversation_url * @param $uid - * @param $item + * @param array $item Data of the item that is to be posted * * @return */ @@ -1184,11 +1180,11 @@ class ostatus { } /** - * @brief + * @brief Checks if the current post is a reshare * - * @param $item + * @param array $item The item array of thw post * - * @return + * @return string The guid if the post is a reshare */ private function get_reshared_guid($item) { $body = trim($item["body"]); @@ -1220,11 +1216,11 @@ class ostatus { } /** - * @brief + * @brief Cleans the body of a post if it contains picture links * - * @param $body + * @param string $body The body * - * @return + * @return string The cleaned body */ private function format_picture_post($body) { $siteinfo = get_attached_data($body); @@ -1256,8 +1252,8 @@ class ostatus { /** * @brief * - * @param $doc - * @param $owner + * @param object $doc XML document + * @param array $owner Contact data of the poster * * @return */ @@ -1317,7 +1313,7 @@ class ostatus { /** * @brief * - * @param $doc + * @param object $doc XML document * @param $root * * @return @@ -1344,9 +1340,9 @@ class ostatus { /** * @brief * - * @param $doc + * @param object $doc XML document * @param $root - * @param $item + * @param array $item Data of the item that is to be posted * * @return */ @@ -1416,8 +1412,8 @@ class ostatus { /** * @brief * - * @param $doc - * @param $owner + * @param object $doc XML document + * @param array $owner Contact data of the poster * * @return */ @@ -1490,7 +1486,7 @@ class ostatus { /** * @brief * - * @param $item + * @param array $item Data of the item that is to be posted * * @return */ @@ -1503,7 +1499,7 @@ class ostatus { /** * @brief * - * @param $item + * @param array $item Data of the item that is to be posted * * @return */ @@ -1516,9 +1512,9 @@ class ostatus { /** * @brief * - * @param $doc - * @param $item - * @param $owner + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster * @param $toplevel * * @return @@ -1540,7 +1536,7 @@ class ostatus { /** * @brief * - * @param $doc + * @param object $doc XML document * @param $contact * * @return @@ -1565,7 +1561,7 @@ class ostatus { * @brief * * @param $url - * @param $owner + * @param array $owner Contact data of the poster * * @return */ @@ -1606,9 +1602,9 @@ class ostatus { /** * @brief * - * @param $doc - * @param $item - * @param $owner + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster * @param $repeated_guid * @param $toplevel * @@ -1673,12 +1669,12 @@ class ostatus { /** * @brief * - * @param $doc - * @param $item - * @param $owner + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster * @param $toplevel * - * @return + * @return object */ private function like_entry($doc, $item, $owner, $toplevel) { @@ -1710,9 +1706,9 @@ class ostatus { /** * @brief * - * @param $doc - * @param $item - * @param $owner + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster * @param $toplevel * * @return @@ -1737,9 +1733,9 @@ class ostatus { /** * @brief * - * @param $doc + * @param object $doc XML document * @param $entry - * @param $owner + * @param array $owner Contact data of the poster * @param $toplevel * * @return @@ -1770,10 +1766,10 @@ class ostatus { /** * @brief * - * @param $doc + * @param object $doc XML document * @param $entry - * @param $item - * @param $owner + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster * @param $title * @param $verb * @param $complete @@ -1812,10 +1808,10 @@ class ostatus { /** * @brief * - * @param $doc + * @param object $doc XML document * @param $entry - * @param $item - * @param $owner + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster * @param $complete * * @return @@ -1975,12 +1971,12 @@ class ostatus { } /** - * @brief + * @brief Creates the XML for a salmon message * - * @param $item - * @param $owner + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster * - * @return + * @return string XML for the salmon */ public static function salmon($item,$owner) { From d0b8f2092de0b839e956043de4442a46044fb59e Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 31 Mar 2016 22:01:56 +0200 Subject: [PATCH 245/273] Some more documentation --- include/ostatus.php | 119 ++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 64 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 0796fcc3b7..60db968f1d 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -144,12 +144,12 @@ class ostatus { } /** - * @brief + * @brief Fetches author data from a given XML string * - * @param $xml + * @param string $xml The XML * @param array $importer user record of the importing user * - * @return + * @return array Array of author related entries for the item */ public static function salmon_author($xml, $importer) { @@ -751,13 +751,13 @@ class ostatus { } /** - * @brief + * @brief Stores an item and completes the thread * - * @param $conversation_url - * @param $uid + * @param string $conversation_url The URI of the conversation + * @param integer $uid The user id * @param array $item Data of the item that is to be posted * - * @return + * @return integer The item id of the posted item array */ private function completion($conversation_url, $uid, $item = array(), $self = "") { @@ -1151,12 +1151,10 @@ class ostatus { } /** - * @brief + * @brief Stores conversation data into the database * - * @param $itemid - * @param $conversation_url - * - * @return + * @param integer $itemid The id of the item + * @param string $conversation_url The uri of the conversation */ private function store_conversation($itemid, $conversation_url) { @@ -1250,12 +1248,12 @@ class ostatus { } /** - * @brief + * @brief Adds the header elements to the XML document * * @param object $doc XML document * @param array $owner Contact data of the poster * - * @return + * @return object header root element */ private function add_header($doc, $owner) { @@ -1311,12 +1309,10 @@ class ostatus { } /** - * @brief + * @brief Add the link to the push hubs to the XML document * * @param object $doc XML document - * @param $root - * - * @return + * @param object $root XML root element where the hub links are added */ public static function hublinks($doc, $root) { $hub = get_config('system','huburl'); @@ -1338,13 +1334,11 @@ class ostatus { } /** - * @brief + * @brief Adds attachement data to the XML document * * @param object $doc XML document - * @param $root + * @param object $root XML root element where the hub links are added * @param array $item Data of the item that is to be posted - * - * @return */ private function get_attachment($doc, $root, $item) { $o = ""; @@ -1410,12 +1404,12 @@ class ostatus { } /** - * @brief + * @brief Adds the author element to the XML document * * @param object $doc XML document * @param array $owner Contact data of the poster * - * @return + * @return object author element */ private function add_author($doc, $owner) { @@ -1484,11 +1478,11 @@ class ostatus { */ /** - * @brief + * @brief Returns the given activity if present - otherwise returns the "post" activity * * @param array $item Data of the item that is to be posted * - * @return + * @return string activity */ function construct_verb($item) { if ($item['verb']) @@ -1497,11 +1491,11 @@ class ostatus { } /** - * @brief + * @brief Returns the given object type if present - otherwise returns the "note" object type * * @param array $item Data of the item that is to be posted * - * @return + * @return string Object type */ function construct_objecttype($item) { if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT))) @@ -1510,14 +1504,14 @@ class ostatus { } /** - * @brief + * @brief Adds an entry element to the XML document * * @param object $doc XML document * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster - * @param $toplevel + * @param bool $toplevel * - * @return + * @return object Entry element */ private function entry($doc, $item, $owner, $toplevel = false) { $repeated_guid = self::get_reshared_guid($item); @@ -1534,12 +1528,12 @@ class ostatus { } /** - * @brief + * @brief Adds a source entry to the XML document * * @param object $doc XML document - * @param $contact + * @param array $contact Array of the contact that is added * - * @return + * @return object Source element */ private function source_entry($doc, $contact) { $source = $doc->createElement("source"); @@ -1558,12 +1552,12 @@ class ostatus { } /** - * @brief + * @brief Fetches contact data from the contact or the gcontact table * - * @param $url + * @param string $url URL of the contact * @param array $owner Contact data of the poster * - * @return + * @return array Contact array */ private function contact_entry($url, $owner) { @@ -1600,15 +1594,15 @@ class ostatus { } /** - * @brief + * @brief Adds an entry element with reshared content * * @param object $doc XML document * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster * @param $repeated_guid - * @param $toplevel + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * - * @return + * @return object Entry element */ private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) { @@ -1667,14 +1661,14 @@ class ostatus { } /** - * @brief + * @brief Adds an entry element with a "like" * * @param object $doc XML document * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster - * @param $toplevel + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * - * @return object + * @return object Entry element with "like" */ private function like_entry($doc, $item, $owner, $toplevel) { @@ -1704,14 +1698,14 @@ class ostatus { } /** - * @brief + * @brief Adds a regular entry element * * @param object $doc XML document * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster - * @param $toplevel + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * - * @return + * @return object Entry element */ private function note_entry($doc, $item, $owner, $toplevel) { @@ -1731,16 +1725,17 @@ class ostatus { } /** - * @brief + * @brief Adds a header element to the XML document * * @param object $doc XML document - * @param $entry + * @param object $entry Entry element * @param array $owner Contact data of the poster - * @param $toplevel + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * - * @return + * @return string The title for the element */ private function entry_header($doc, &$entry, $owner, $toplevel) { + /// @todo Check if this title stuff is really needed (I guess not) if (!$toplevel) { $entry = $doc->createElement("entry"); $title = sprintf("New note by %s", $owner["nick"]); @@ -1764,17 +1759,15 @@ class ostatus { } /** - * @brief + * @brief Adds elements to the XML document * * @param object $doc XML document - * @param $entry + * @param object $entry Entry element where the content is added * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster - * @param $title - * @param $verb - * @param $complete - * - * @return + * @param string $title Title for the post + * @param string $verb The activity verb + * @param bool $complete Add the "status_net" element? */ private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) { @@ -1813,8 +1806,6 @@ class ostatus { * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster * @param $complete - * - * @return */ private function entry_footer($doc, $entry, $item, $owner, $complete = true) { @@ -1913,13 +1904,13 @@ class ostatus { } /** - * @brief + * @brief Creates the XML feed for a given nickname * - * @param $a - * @param $owner_nick - * @param $last_update + * @param app $a The application class + * @param string $owner_nick Nickname of the feed owner + * @param string $last_update Date of the last update * - * @return + * @return string XML feed */ public static function feed(&$a, $owner_nick, $last_update) { From ffcc5f372f5626c592ada8de2b7eacfbc61f6622 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Thu, 31 Mar 2016 22:24:54 +0200 Subject: [PATCH 246/273] Some more documentation --- include/ostatus.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 60db968f1d..e31b4474da 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -187,6 +187,7 @@ class ostatus { * @param array $hub Called by reference, returns the fetched hub data */ public static function import($xml,$importer,&$contact, &$hub) { + /// @todo this function is too long. It has to be split in many parts logger("Import OStatus message", LOGGER_DEBUG); @@ -761,6 +762,7 @@ class ostatus { */ private function completion($conversation_url, $uid, $item = array(), $self = "") { + /// @todo This function is totally ugly and has to be rewritten totally $item_stored = -1; @@ -1728,7 +1730,7 @@ class ostatus { * @brief Adds a header element to the XML document * * @param object $doc XML document - * @param object $entry Entry element + * @param object $entry The entry element where the elements are added * @param array $owner Contact data of the poster * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * @@ -1799,10 +1801,10 @@ class ostatus { } /** - * @brief + * @brief Adds the elements at the foot of an entry to the XML document * * @param object $doc XML document - * @param $entry + * @param object $entry The entry element where the elements are added * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster * @param $complete From 416a0e5ec2684f1bfe029da9438b2782127d9355 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 1 Apr 2016 21:09:52 +0200 Subject: [PATCH 247/273] Don't optimize the tables when the maximum size is lower than zero --- include/cron.php | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/include/cron.php b/include/cron.php index c60284b738..a2482ff300 100644 --- a/include/cron.php +++ b/include/cron.php @@ -335,35 +335,37 @@ function cron_clear_cache(&$a) { if ($max_tablesize == 0) $max_tablesize = 100 * 1000000; // Default are 100 MB - // Minimum fragmentation level in percent - $fragmentation_level = intval(get_config('system','optimize_fragmentation')) / 100; - if ($fragmentation_level == 0) - $fragmentation_level = 0.3; // Default value is 30% + if ($max_tablesize > 0) { + // Minimum fragmentation level in percent + $fragmentation_level = intval(get_config('system','optimize_fragmentation')) / 100; + if ($fragmentation_level == 0) + $fragmentation_level = 0.3; // Default value is 30% - // Optimize some tables that need to be optimized - $r = q("SHOW TABLE STATUS"); - foreach($r as $table) { + // Optimize some tables that need to be optimized + $r = q("SHOW TABLE STATUS"); + foreach($r as $table) { - // Don't optimize tables that are too large - if ($table["Data_length"] > $max_tablesize) - continue; + // Don't optimize tables that are too large + if ($table["Data_length"] > $max_tablesize) + continue; - // Don't optimize empty tables - if ($table["Data_length"] == 0) - continue; + // Don't optimize empty tables + if ($table["Data_length"] == 0) + continue; - // Calculate fragmentation - $fragmentation = $table["Data_free"] / ($table["Data_length"] + $table["Index_length"]); + // Calculate fragmentation + $fragmentation = $table["Data_free"] / ($table["Data_length"] + $table["Index_length"]); - logger("Table ".$table["Name"]." - Fragmentation level: ".round($fragmentation * 100, 2), LOGGER_DEBUG); + logger("Table ".$table["Name"]." - Fragmentation level: ".round($fragmentation * 100, 2), LOGGER_DEBUG); - // Don't optimize tables that needn't to be optimized - if ($fragmentation < $fragmentation_level) - continue; + // Don't optimize tables that needn't to be optimized + if ($fragmentation < $fragmentation_level) + continue; - // So optimize it - logger("Optimize Table ".$table["Name"], LOGGER_DEBUG); - q("OPTIMIZE TABLE `%s`", dbesc($table["Name"])); + // So optimize it + logger("Optimize Table ".$table["Name"], LOGGER_DEBUG); + q("OPTIMIZE TABLE `%s`", dbesc($table["Name"])); + } } set_config('system','cache_last_cleared', time()); From eeb462cd0460d029a8b9e29f3dd122eb16befec1 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Fri, 1 Apr 2016 21:41:37 +0200 Subject: [PATCH 248/273] Only update the contact entry with uid=0 --- include/Scrape.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Scrape.php b/include/Scrape.php index deff0b080f..3fead0c415 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -845,7 +845,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { /// It should only be updated if the existing picture isn't existing anymore. if (($result['network'] != NETWORK_FEED) AND $result["addr"] AND $result["name"] AND $result["nick"]) q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s', - `name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self`", + `name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0", dbesc($result["addr"]), dbesc($result["alias"]), dbesc($result["name"]), From 08fb662b4a7b9c94a5882b0c6f47ed463dfb0ba2 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 2 Apr 2016 00:24:56 +0200 Subject: [PATCH 249/273] OStatus: Salmon now works with "likes"/The alias is stored now as well --- include/notifier.php | 16 +++++++++++++++- include/ostatus.php | 13 +++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/include/notifier.php b/include/notifier.php index 18a617ac2f..ffbb22e7bf 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -229,7 +229,7 @@ function notifier_run(&$argv, &$argc){ $parent = $items[0]; - $thr_parent = q("SELECT `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d", + $thr_parent = q("SELECT `network`, `author-link`, `owner-link` FROM `item` WHERE `uri` = '%s' AND `uid` = %d", dbesc($target_item["thr-parent"]), intval($target_item["uid"])); logger('Parent is '.$parent['network'].'. Thread parent is '.$thr_parent[0]['network'], LOGGER_DEBUG); @@ -390,6 +390,20 @@ function notifier_run(&$argv, &$argc){ logger('Some parent is OStatus for '.$target_item["guid"], LOGGER_DEBUG); + // Send a salmon to the parent author + $probed_contact = probe_url($thr_parent[0]['author-link']); + if ($probed_contact["notify"] != "") { + logger('Notify parent author '.$probed_contact["url"].': '.$probed_contact["notify"]); + $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"]; + } + + // Send a salmon to the parent owner + $probed_contact = probe_url($thr_parent[0]['owner-link']); + if ($probed_contact["notify"] != "") { + logger('Notify parent owner '.$probed_contact["url"].': '.$probed_contact["notify"]); + $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"]; + } + // Send a salmon notification to every person we mentioned in the post $arr = explode(',',$target_item['tag']); foreach($arr as $x) { diff --git a/include/ostatus.php b/include/ostatus.php index e31b4474da..4775e2ccb9 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -44,8 +44,7 @@ class ostatus { $author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue; - // Preserve the value - $authorlink = $author["author-link"]; + $aliaslink = $author["author-link"]; $alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes; if (is_object($alternate)) @@ -55,7 +54,7 @@ class ostatus { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'", intval($importer["uid"]), dbesc(normalise_link($author["author-link"])), - dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET)); + dbesc(normalise_link($aliaslink)), dbesc(NETWORK_STATUSNET)); if ($r) { $contact = $r[0]; $author["contact-id"] = $r[0]["id"]; @@ -117,12 +116,14 @@ class ostatus { if ($value != "") $contact["location"] = $value; - if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { + if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR + ($contact["alias"] != $r[0]["alias"]) OR ($contact["location"] != $r[0]["location"])) { logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", - dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `alias` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["alias"]), + dbesc($contact["about"]), dbesc($contact["location"]), dbesc(datetime_convert()), intval($contact["id"])); poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], From bc05984786547a1440261be932d8d7f5498d74ae Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 2 Apr 2016 09:06:10 +0200 Subject: [PATCH 250/273] Only update contact when scrape runs with "probe_normal" --- include/Scrape.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Scrape.php b/include/Scrape.php index 3fead0c415..9913f360d6 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -843,15 +843,15 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { /// @todo temporary fix - we need a real contact update function that updates only changing fields /// The biggest problem is the avatar picture that could have a reduced image size. /// It should only be updated if the existing picture isn't existing anymore. - if (($result['network'] != NETWORK_FEED) AND $result["addr"] AND $result["name"] AND $result["nick"]) + if (($result['network'] != NETWORK_FEED) AND ($mode == PROBE_NORMAL) AND + $result["addr"] AND $result["name"] AND $result["nick"]) q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s', - `name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0", + `success_update` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0", dbesc($result["addr"]), dbesc($result["alias"]), dbesc($result["name"]), dbesc($result["nick"]), dbesc(datetime_convert()), - dbesc(datetime_convert()), dbesc(normalise_link($result['url'])) ); } From fd965b046aa12bad60330c4c5655dd21a325f998 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 2 Apr 2016 14:10:40 +0200 Subject: [PATCH 251/273] Update contact data for uid=0 at feed import --- include/ostatus.php | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index 4775e2ccb9..d6e2e17735 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -90,13 +90,20 @@ class ostatus { // Only update the contacts if it is an OStatus contact if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { + // Update contact data - $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; - if ($value != "") - $contact["notify"] = $value; + // This query doesn't seem to work + // $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; + // if ($value != "") + // $contact["notify"] = $value; - $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue; + // This query doesn't seem to work as well - I hate these queries + // $value = $xpath->query("atom:link[@rel='self' and @type='application/atom+xml']", $context)->item(0)->nodeValue; + // if ($value != "") + // $contact["poll"] = $value; + + $value = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue; if ($value != "") $contact["alias"] = $value; @@ -136,6 +143,24 @@ class ostatus { update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); } + // Ensure that we are having this contact (with uid=0) + $cid = get_contact($author["author-link"], 0); + + if ($cid) { + // Update it with the current values + q("UPDATE `contact` SET `url` = '%s', `name` = '%s', `nick` = '%s', `alias` = '%s', + `about` = '%s', `location` = '%s', `notify` = '%s', `poll` = '%s', + `success_update` = '%s', `last-update` = '%s' + WHERE `id` = %d", + dbesc($author["author-link"]), dbesc($contact["name"]), dbesc($contact["nick"]), + dbesc($contact["alias"]), dbesc($contact["about"]), dbesc($contact["location"]), + dbesc($contact["notify"]), dbesc($contact["poll"]), + dbesc(datetime_convert()), dbesc(datetime_convert()), intval($cid)); + + // Update the avatar + update_contact_avatar($author["author-avatar"], 0, $cid); + } + $contact["generation"] = 2; $contact["photo"] = $author["author-avatar"]; update_gcontact($contact); @@ -1586,8 +1611,10 @@ class ostatus { if (!isset($contact["poll"])) { $data = probe_url($url); - $contact["alias"] = $data["alias"]; $contact["poll"] = $data["poll"]; + + if (!$contact["alias"]) + $contact["alias"] = $data["alias"]; } if (!isset($contact["alias"])) From b4b62493e6f43ed85d9c9cecabf118814db8d454 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 2 Apr 2016 15:41:55 +0200 Subject: [PATCH 252/273] Bugfix: The nickname vanished/better way to fetch the alias --- include/Scrape.php | 21 +++++++++++++++------ include/cron.php | 3 +++ include/feed.php | 5 ++++- mod/noscrape.php | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/include/Scrape.php b/include/Scrape.php index 9913f360d6..ac95c0d5d3 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -23,13 +23,15 @@ function scrape_dfrn($url, $dont_probe = false) { if (is_array($noscrapedata)) { if ($noscrapedata["nick"] != "") return($noscrapedata); + else + unset($noscrapedata["nick"]); } else $noscrapedata = array(); } $s = fetch_url($url); - if(! $s) + if (!$s) return $ret; if (!$dont_probe) { @@ -703,6 +705,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { if (($vcard["nick"] == "") AND ($data["header"]["author-nick"] != "")) $vcard["nick"] = $data["header"]["author-nick"]; + if (($network == NETWORK_OSTATUS) AND ($data["header"]["author-id"] != "")) + $alias = $data["header"]["author-id"]; + if(!$profile AND ($data["header"]["author-link"] != "") AND !in_array($network, array("", NETWORK_FEED))) $profile = $data["header"]["author-link"]; } @@ -844,13 +849,17 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { /// The biggest problem is the avatar picture that could have a reduced image size. /// It should only be updated if the existing picture isn't existing anymore. if (($result['network'] != NETWORK_FEED) AND ($mode == PROBE_NORMAL) AND - $result["addr"] AND $result["name"] AND $result["nick"]) - q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s', - `success_update` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0", - dbesc($result["addr"]), - dbesc($result["alias"]), + $result["name"] AND $result["nick"] AND $result["url"] AND $result["addr"] AND $result["poll"]) + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `url` = '%s', `addr` = '%s', + `notify` = '%s', `poll` = '%s', `alias` = '%s', `success_update` = '%s' + WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0", dbesc($result["name"]), dbesc($result["nick"]), + dbesc($result["url"]), + dbesc($result["addr"]), + dbesc($result["notify"]), + dbesc($result["poll"]), + dbesc($result["alias"]), dbesc(datetime_convert()), dbesc(normalise_link($result['url'])) ); diff --git a/include/cron.php b/include/cron.php index a2482ff300..e2f4102804 100644 --- a/include/cron.php +++ b/include/cron.php @@ -405,6 +405,9 @@ function cron_repair_database() { // This call is very "cheap" so we can do it at any time without a problem q("UPDATE `item` INNER JOIN `item` AS `parent` ON `parent`.`uri` = `item`.`parent-uri` AND `parent`.`uid` = `item`.`uid` SET `item`.`parent` = `parent`.`id` WHERE `item`.`parent` = 0"); + // There was an issue where the nick vanishes from the contact table + q("UPDATE `contact` INNER JOIN `user` ON `contact`.`uid` = `user`.`uid` SET `nick` = `nickname` WHERE `self` AND `nick`=''"); + /// @todo /// - remove thread entries without item /// - remove sign entries without item diff --git a/include/feed.php b/include/feed.php index 04cfba75a6..293de3cc96 100644 --- a/include/feed.php +++ b/include/feed.php @@ -54,8 +54,10 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { if ($attributes->name == "href") $author["author-link"] = $attributes->textContent; + $author["author-id"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue; + if ($author["author-link"] == "") - $author["author-link"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue; + $author["author-link"] = $author["author-id"]; if ($author["author-link"] == "") { $self = $xpath->query("atom:link[@rel='self']")->item(0)->attributes; @@ -127,6 +129,7 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) { // This is no field in the item table. So we have to unset it. unset($author["author-nick"]); + unset($author["author-id"]); } $header = array(); diff --git a/mod/noscrape.php b/mod/noscrape.php index 1f7105b769..4be1a740cd 100644 --- a/mod/noscrape.php +++ b/mod/noscrape.php @@ -28,7 +28,7 @@ function noscrape_init(&$a) { $json_info = array( 'fn' => $a->profile['name'], 'addr' => $a->profile['addr'], - 'nick' => $a->user['nickname'], + 'nick' => $which, 'key' => $a->profile['pubkey'], 'homepage' => $a->get_baseurl()."/profile/{$which}", 'comm' => (x($a->profile,'page-flags')) && ($a->profile['page-flags'] == PAGE_COMMUNITY), From 68b88c038426bfbe97010f2b85a4675fd49d299f Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 2 Apr 2016 15:49:57 +0200 Subject: [PATCH 253/273] Notify and poll aren't fetched at the moment --- include/ostatus.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/ostatus.php b/include/ostatus.php index d6e2e17735..bf14536f9e 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -149,12 +149,11 @@ class ostatus { if ($cid) { // Update it with the current values q("UPDATE `contact` SET `url` = '%s', `name` = '%s', `nick` = '%s', `alias` = '%s', - `about` = '%s', `location` = '%s', `notify` = '%s', `poll` = '%s', + `about` = '%s', `location` = '%s', `success_update` = '%s', `last-update` = '%s' WHERE `id` = %d", dbesc($author["author-link"]), dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["alias"]), dbesc($contact["about"]), dbesc($contact["location"]), - dbesc($contact["notify"]), dbesc($contact["poll"]), dbesc(datetime_convert()), dbesc(datetime_convert()), intval($cid)); // Update the avatar From ce2c1b4fc1b9328e8f968083062b921e02b4fa59 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 2 Apr 2016 17:43:53 +0200 Subject: [PATCH 254/273] Small bugfix: We always liked the parent --- include/ostatus.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/ostatus.php b/include/ostatus.php index bf14536f9e..b798a605f9 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -1712,7 +1712,8 @@ class ostatus { $as_object = $doc->createElement("activity:object"); - $parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"])); + $parent = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d", + dbesc($item["thr-parent"]), intval($item["uid"])); $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); xml::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0])); From beb1e040696ae53b58aebdd2f639b0eb90c639fd Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 3 Apr 2016 13:48:31 +0200 Subject: [PATCH 255/273] New field in item table ("shadow") that indicates if there is a shadow entry --- boot.php | 8 +-- include/Contact.php | 54 ----------------- include/cron.php | 5 +- include/dbstructure.php | 1 + include/post_update.php | 129 ++++++++++++++++++++++++++++++++++++++++ include/threads.php | 8 ++- update.php | 2 +- 7 files changed, 145 insertions(+), 62 deletions(-) create mode 100644 include/post_update.php diff --git a/boot.php b/boot.php index a70c7bf616..13cc2aaf5b 100644 --- a/boot.php +++ b/boot.php @@ -6,11 +6,11 @@ /** * Friendica - * + * * Friendica is a communications platform for integrated social communications * utilising decentralised communications and linkage to several indie social * projects - as well as popular mainstream providers. - * + * * Our mission is to free our friends and families from the clutches of * data-harvesting corporations, and pave the way to a future where social * communications are free and open and flow between alternate providers as @@ -18,7 +18,7 @@ */ require_once('include/autoloader.php'); - + require_once('include/config.php'); require_once('include/network.php'); require_once('include/plugin.php'); @@ -38,7 +38,7 @@ define ( 'FRIENDICA_PLATFORM', 'Friendica'); define ( 'FRIENDICA_CODENAME', 'Asparagus'); define ( 'FRIENDICA_VERSION', '3.5-dev' ); define ( 'DFRN_PROTOCOL_VERSION', '2.23' ); -define ( 'DB_UPDATE_VERSION', 1194 ); +define ( 'DB_UPDATE_VERSION', 1195 ); /** * @brief Constant with a HTML line break. diff --git a/include/Contact.php b/include/Contact.php index d76c8f826c..79a14ab581 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -555,60 +555,6 @@ function posts_from_gcontact($a, $gcontact_id) { return $o; } -/** - * @brief set the gcontact-id in all item entries - * - * This job has to be started multiple times until all entries are set. - * It isn't started in the update function since it would consume too much time and can be done in the background. - */ -function item_set_gcontact() { - define ('POST_UPDATE_VERSION', 1192); - - // Was the script completed? - if (get_config("system", "post_update_version") >= POST_UPDATE_VERSION) - return; - - // Check if the first step is done (Setting "gcontact-id" in the item table) - $r = q("SELECT `author-link`, `author-name`, `author-avatar`, `uid`, `network` FROM `item` WHERE `gcontact-id` = 0 LIMIT 1000"); - if (!$r) { - // Are there unfinished entries in the thread table? - $r = q("SELECT COUNT(*) AS `total` FROM `thread` - INNER JOIN `item` ON `item`.`id` =`thread`.`iid` - WHERE `thread`.`gcontact-id` = 0 AND - (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)"); - - if ($r AND ($r[0]["total"] == 0)) { - set_config("system", "post_update_version", POST_UPDATE_VERSION); - return false; - } - - // Update the thread table from the item table - q("UPDATE `thread` INNER JOIN `item` ON `item`.`id`=`thread`.`iid` - SET `thread`.`gcontact-id` = `item`.`gcontact-id` - WHERE `thread`.`gcontact-id` = 0 AND - (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)"); - - return false; - } - - $item_arr = array(); - foreach ($r AS $item) { - $index = $item["author-link"]."-".$item["uid"]; - $item_arr[$index] = array("author-link" => $item["author-link"], - "uid" => $item["uid"], - "network" => $item["network"]); - } - - // Set the "gcontact-id" in the item table and add a new gcontact entry if needed - foreach($item_arr AS $item) { - $gcontact_id = get_gcontact_id(array("url" => $item['author-link'], "network" => $item['network'], - "photo" => $item['author-avatar'], "name" => $item['author-name'])); - q("UPDATE `item` SET `gcontact-id` = %d WHERE `uid` = %d AND `author-link` = '%s' AND `gcontact-id` = 0", - intval($gcontact_id), intval($item["uid"]), dbesc($item["author-link"])); - } - return true; -} - /** * @brief Returns posts from a given contact * diff --git a/include/cron.php b/include/cron.php index e2f4102804..4eb76f1ce2 100644 --- a/include/cron.php +++ b/include/cron.php @@ -35,6 +35,7 @@ function cron_run(&$argv, &$argc){ require_once('include/email.php'); require_once('include/socgraph.php'); require_once('mod/nodeinfo.php'); + require_once('include/post_update.php'); load_config('config'); load_config('system'); @@ -106,8 +107,8 @@ function cron_run(&$argv, &$argc){ // Check every conversation ostatus::check_conversations(false); - // Set the gcontact-id in the item table if missing - item_set_gcontact(); + // Do post update functions + post_update(); // update nodeinfo data nodeinfo_cron(); diff --git a/include/dbstructure.php b/include/dbstructure.php index e34e409023..33e0c7dc8c 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -783,6 +783,7 @@ function db_definition() { "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" => ""), + "shadow" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"), "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), "edited" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), "commented" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), diff --git a/include/post_update.php b/include/post_update.php new file mode 100644 index 0000000000..2143ac3d6f --- /dev/null +++ b/include/post_update.php @@ -0,0 +1,129 @@ +<?php +/** + * @brief Calls the post update functions + */ +function post_update() { + + if (!post_update_1192()) + return; + + if (!post_update_1195()) + return; +} + +/** + * @brief set the gcontact-id in all item entries + * + * This job has to be started multiple times until all entries are set. + * It isn't started in the update function since it would consume too much time and can be done in the background. + * + * @return bool "true" when the job is done + */ +function post_update_1192() { + + // Was the script completed? + if (get_config("system", "post_update_version") >= 1192) + return true; + + // Check if the first step is done (Setting "gcontact-id" in the item table) + $r = q("SELECT `author-link`, `author-name`, `author-avatar`, `uid`, `network` FROM `item` WHERE `gcontact-id` = 0 LIMIT 1000"); + if (!$r) { + // Are there unfinished entries in the thread table? + $r = q("SELECT COUNT(*) AS `total` FROM `thread` + INNER JOIN `item` ON `item`.`id` =`thread`.`iid` + WHERE `thread`.`gcontact-id` = 0 AND + (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)"); + + if ($r AND ($r[0]["total"] == 0)) { + set_config("system", "post_update_version", 1192); + return true; + } + + // Update the thread table from the item table + q("UPDATE `thread` INNER JOIN `item` ON `item`.`id`=`thread`.`iid` + SET `thread`.`gcontact-id` = `item`.`gcontact-id` + WHERE `thread`.`gcontact-id` = 0 AND + (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)"); + + return false; + } + + $item_arr = array(); + foreach ($r AS $item) { + $index = $item["author-link"]."-".$item["uid"]; + $item_arr[$index] = array("author-link" => $item["author-link"], + "uid" => $item["uid"], + "network" => $item["network"]); + } + + // Set the "gcontact-id" in the item table and add a new gcontact entry if needed + foreach($item_arr AS $item) { + $gcontact_id = get_gcontact_id(array("url" => $item['author-link'], "network" => $item['network'], + "photo" => $item['author-avatar'], "name" => $item['author-name'])); + q("UPDATE `item` SET `gcontact-id` = %d WHERE `uid` = %d AND `author-link` = '%s' AND `gcontact-id` = 0", + intval($gcontact_id), intval($item["uid"]), dbesc($item["author-link"])); + } + return false; +} + +/** + * @brief Updates the "shadow" field in the item table + * + * @return bool "true" when the job is done + */ +function post_update_1195() { + + // Was the script completed? + if (get_config("system", "post_update_version") >= 1195) + return true; + + $end_id = get_config("system", "post_update_1195_end"); + if (!$end_id) { + $r = q("SELECT `id` FROM `item` WHERE `uid` != 0 ORDER BY `id` DESC LIMIT 1"); + if ($r) { + set_config("system", "post_update_1195_end", $r[0]["id"]); + $end_id = get_config("system", "post_update_1195_end"); + } + } + + $start_id = get_config("system", "post_update_1195_start"); + + $query1 = "SELECT `item`.`id` FROM `item` "; + + $query2 = "INNER JOIN `item` AS `shadow` ON `item`.`uri` = `shadow`.`uri` AND `shadow`.`uid` = 0 "; + + $query3 = "WHERE `item`.`uid` != 0 AND `item`.`id` >= %d AND `item`.`id` <= %d + AND `item`.`visible` AND NOT `item`.`private` + AND NOT `item`.`deleted` AND NOT `item`.`moderated` + AND `item`.`network` IN ('%s', '%s', '%s', '') + AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' + AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' + AND `item`.`shadow` = 0"; + + $r = q($query1.$query2.$query3." ORDER BY `item`.`id` LIMIT 1", + intval($start_id), intval($end_id), + dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); + if (!$r) { + set_config("system", "post_update_version", 1195); + return true; + } else { + set_config("system", "post_update_1195_start", $r[0]["id"]); + $start_id = get_config("system", "post_update_1195_start"); + } + + + $r = q($query1.$query2.$query3." ORDER BY `item`.`id` LIMIT 10000,1", + intval($start_id), intval($end_id), + dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); + if ($r) + $pos_id = $r[0]["id"]; + else + $pos_id = $end_id; + + logger("Progress: Start: ".$start_id." position: ".$pos_id." end: ".$end_id, LOGGER_DEBUG); + + $r = q("UPDATE `item` ".$query2." SET `item`.`shadow` = `shadow`.`id` ".$query3, + intval($start_id), intval($pos_id), + dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); +} +?> diff --git a/include/threads.php b/include/threads.php index 0320eaa018..21fdb0df34 100644 --- a/include/threads.php +++ b/include/threads.php @@ -65,13 +65,19 @@ function add_thread($itemid, $onlyshadow = false) { require_once("include/Contact.php"); unset($item[0]['id']); + unset($item[0]['shadow']); $item[0]['uid'] = 0; $item[0]['origin'] = 0; $item[0]['contact-id'] = get_contact($item[0]['author-link'], 0); $public_shadow = item_store($item[0], false, false, true); logger("add_thread: Stored public shadow for post ".$itemid." under id ".$public_shadow, LOGGER_DEBUG); - } + } else + $public_shadow = $r[0]["id"]; + + if ($public_shadow > 0) + q("UPDATE `item` SET `shadow` = %d WHERE `id` = %d", intval($public_shadow), intval($itemid)); + } } diff --git a/update.php b/update.php index 0689aa19b3..dad94f271b 100644 --- a/update.php +++ b/update.php @@ -1,6 +1,6 @@ <?php -define( 'UPDATE_VERSION' , 1194 ); +define( 'UPDATE_VERSION' , 1195 ); /** * From 9f57425d0880cc49a09562b1b63d502463440c0a Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 3 Apr 2016 16:36:05 +0200 Subject: [PATCH 256/273] Post update ist now done. --- boot.php | 2 +- include/dbstructure.php | 1 - include/items.php | 4 ++-- include/post_update.php | 40 ++++++++++++++++++++++++++-------------- mod/search.php | 5 ++--- update.php | 2 +- 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/boot.php b/boot.php index 13cc2aaf5b..58b4bc0983 100644 --- a/boot.php +++ b/boot.php @@ -38,7 +38,7 @@ define ( 'FRIENDICA_PLATFORM', 'Friendica'); define ( 'FRIENDICA_CODENAME', 'Asparagus'); define ( 'FRIENDICA_VERSION', '3.5-dev' ); define ( 'DFRN_PROTOCOL_VERSION', '2.23' ); -define ( 'DB_UPDATE_VERSION', 1195 ); +define ( 'DB_UPDATE_VERSION', 1194 ); /** * @brief Constant with a HTML line break. diff --git a/include/dbstructure.php b/include/dbstructure.php index 33e0c7dc8c..e34e409023 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -783,7 +783,6 @@ function db_definition() { "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" => ""), - "shadow" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"), "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), "edited" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), "commented" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), diff --git a/include/items.php b/include/items.php index 233d72d133..4627b10ca2 100644 --- a/include/items.php +++ b/include/items.php @@ -707,9 +707,9 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa if ($arr["uid"] == 0) { $arr["global"] = true; - q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"])); + q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"])); } else { - $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"])); + $isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"])); $arr["global"] = (count($isglobal) > 0); } diff --git a/include/post_update.php b/include/post_update.php index 2143ac3d6f..2bdfe1f6fd 100644 --- a/include/post_update.php +++ b/include/post_update.php @@ -1,4 +1,8 @@ <?php +/** + * @file include/post_update.php + */ + /** * @brief Calls the post update functions */ @@ -7,7 +11,7 @@ function post_update() { if (!post_update_1192()) return; - if (!post_update_1195()) + if (!post_update_1194()) return; } @@ -67,26 +71,30 @@ function post_update_1192() { } /** - * @brief Updates the "shadow" field in the item table + * @brief Updates the "global" field in the item table * * @return bool "true" when the job is done */ -function post_update_1195() { +function post_update_1194() { // Was the script completed? - if (get_config("system", "post_update_version") >= 1195) + if (get_config("system", "post_update_version") >= 1194) return true; - $end_id = get_config("system", "post_update_1195_end"); + logger("Start", LOGGER_DEBUG); + + $end_id = get_config("system", "post_update_1194_end"); if (!$end_id) { $r = q("SELECT `id` FROM `item` WHERE `uid` != 0 ORDER BY `id` DESC LIMIT 1"); if ($r) { - set_config("system", "post_update_1195_end", $r[0]["id"]); - $end_id = get_config("system", "post_update_1195_end"); + set_config("system", "post_update_1194_end", $r[0]["id"]); + $end_id = get_config("system", "post_update_1194_end"); } } - $start_id = get_config("system", "post_update_1195_start"); + logger("End ID: ".$end_id, LOGGER_DEBUG); + + $start_id = get_config("system", "post_update_1194_start"); $query1 = "SELECT `item`.`id` FROM `item` "; @@ -98,21 +106,23 @@ function post_update_1195() { AND `item`.`network` IN ('%s', '%s', '%s', '') AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`shadow` = 0"; + AND NOT `item`.`global`"; $r = q($query1.$query2.$query3." ORDER BY `item`.`id` LIMIT 1", intval($start_id), intval($end_id), dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); if (!$r) { - set_config("system", "post_update_version", 1195); + set_config("system", "post_update_version", 1194); + logger("Update is done", LOGGER_DEBUG); return true; } else { - set_config("system", "post_update_1195_start", $r[0]["id"]); - $start_id = get_config("system", "post_update_1195_start"); + set_config("system", "post_update_1194_start", $r[0]["id"]); + $start_id = get_config("system", "post_update_1194_start"); } + logger("Start ID: ".$start_id, LOGGER_DEBUG); - $r = q($query1.$query2.$query3." ORDER BY `item`.`id` LIMIT 10000,1", + $r = q($query1.$query2.$query3." ORDER BY `item`.`id` LIMIT 1000,1", intval($start_id), intval($end_id), dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); if ($r) @@ -122,8 +132,10 @@ function post_update_1195() { logger("Progress: Start: ".$start_id." position: ".$pos_id." end: ".$end_id, LOGGER_DEBUG); - $r = q("UPDATE `item` ".$query2." SET `item`.`shadow` = `shadow`.`id` ".$query3, + $r = q("UPDATE `item` ".$query2." SET `item`.`global` = 1 ".$query3, intval($start_id), intval($pos_id), dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS)); + + logger("Done", LOGGER_DEBUG); } ?> diff --git a/mod/search.php b/mod/search.php index 1776a92552..790f577ba6 100644 --- a/mod/search.php +++ b/mod/search.php @@ -217,11 +217,10 @@ function search_content(&$a) { FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND NOT `contact`.`blocked` AND NOT `contact`.`pending` WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` - AND (`item`.`uid` = 0 OR (`item`.`uid` = %s AND (`item`.`private` OR NOT `item`.`network` IN ('%s', '%s', '%s')))) + AND (`item`.`uid` = 0 OR (`item`.`uid` = %s AND NOT `item`.`global`)) $sql_extra GROUP BY `item`.`uri` ORDER BY `item`.`id` DESC LIMIT %d , %d ", - intval(local_user()), dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DIASPORA), - intval($a->pager['start']), intval($a->pager['itemspage'])); + intval(local_user()), intval($a->pager['start']), intval($a->pager['itemspage'])); } if(! count($r)) { diff --git a/update.php b/update.php index dad94f271b..0689aa19b3 100644 --- a/update.php +++ b/update.php @@ -1,6 +1,6 @@ <?php -define( 'UPDATE_VERSION' , 1195 ); +define( 'UPDATE_VERSION' , 1194 ); /** * From f192767d7ad1eace0a9f5e33ba8d2625782ad562 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 3 Apr 2016 16:38:32 +0200 Subject: [PATCH 257/273] Some reverted stuff --- include/threads.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/include/threads.php b/include/threads.php index 21fdb0df34..0320eaa018 100644 --- a/include/threads.php +++ b/include/threads.php @@ -65,19 +65,13 @@ function add_thread($itemid, $onlyshadow = false) { require_once("include/Contact.php"); unset($item[0]['id']); - unset($item[0]['shadow']); $item[0]['uid'] = 0; $item[0]['origin'] = 0; $item[0]['contact-id'] = get_contact($item[0]['author-link'], 0); $public_shadow = item_store($item[0], false, false, true); logger("add_thread: Stored public shadow for post ".$itemid." under id ".$public_shadow, LOGGER_DEBUG); - } else - $public_shadow = $r[0]["id"]; - - if ($public_shadow > 0) - q("UPDATE `item` SET `shadow` = %d WHERE `id` = %d", intval($public_shadow), intval($itemid)); - + } } } From 94e8139c7e5bd90d814e18c6a7ce2b4d44008100 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 3 Apr 2016 18:18:36 +0200 Subject: [PATCH 258/273] issue 2247: Update more fields when relocation the server --- mod/admin.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/mod/admin.php b/mod/admin.php index 28c8ed15c2..2fc9c48a78 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -492,6 +492,10 @@ function admin_page_site_post(&$a) { $old_url = $a->get_baseurl(true); + // Generate host names for relocation the addresses in the format user@address.tld + $new_host = str_replace("http://", "@", normalise_link($new_url)); + $old_host = str_replace("http://", "@", normalise_link($old_url)); + function update_table($table_name, $fields, $old_url, $new_url) { global $db, $a; @@ -516,11 +520,16 @@ function admin_page_site_post(&$a) { } // update tables + // update profile links in the format "http://server.tld" update_table("profile", array('photo', 'thumb'), $old_url, $new_url); update_table("term", array('url'), $old_url, $new_url); - update_table("contact", array('photo','thumb','micro','url','nurl','request','notify','poll','confirm','poco'), $old_url, $new_url); - update_table("gcontact", array('photo','url','nurl','server_url'), $old_url, $new_url); - update_table("item", array('owner-link','owner-avatar','author-name','author-link','author-avatar','body','plink','tag'), $old_url, $new_url); + update_table("contact", array('photo','thumb','micro','url','nurl','alias','request','notify','poll','confirm','poco', 'avatar'), $old_url, $new_url); + update_table("gcontact", array('url','nurl','photo','server_url','notify','alias'), $old_url, $new_url); + update_table("item", array('owner-link','owner-avatar','author-link','author-avatar','body','plink','tag'), $old_url, $new_url); + + // update profile addresses in the format "user@server.tld" + update_table("contact", array('addr'), $old_host, $new_host); + update_table("gcontact", array('connect','addr'), $old_host, $new_host); // update config $a->set_baseurl($new_url); From 3fdaafd54a8a24c5f265eb0dcdc218a51901a4eb Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 3 Apr 2016 18:49:41 +0200 Subject: [PATCH 259/273] Some more documentation --- include/cron.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/cron.php b/include/cron.php index 4eb76f1ce2..00dd500704 100644 --- a/include/cron.php +++ b/include/cron.php @@ -107,7 +107,8 @@ function cron_run(&$argv, &$argc){ // Check every conversation ostatus::check_conversations(false); - // Do post update functions + // Call possible post update functions + // see include/post_update.php for more details post_update(); // update nodeinfo data From 1f61efc7fef9196aaba9082d6b0e92e371da088c Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 3 Apr 2016 19:29:35 +0200 Subject: [PATCH 260/273] Scrape: Changed detection for the profile link --- include/Scrape.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/include/Scrape.php b/include/Scrape.php index ac95c0d5d3..68926a997e 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -705,10 +705,14 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { if (($vcard["nick"] == "") AND ($data["header"]["author-nick"] != "")) $vcard["nick"] = $data["header"]["author-nick"]; - if (($network == NETWORK_OSTATUS) AND ($data["header"]["author-id"] != "")) - $alias = $data["header"]["author-id"]; + if ($network == NETWORK_OSTATUS) { + if ($data["header"]["author-id"] != "") + $alias = $data["header"]["author-id"]; - if(!$profile AND ($data["header"]["author-link"] != "") AND !in_array($network, array("", NETWORK_FEED))) + if ($data["header"]["author-link"] != "") + $profile = $data["header"]["author-link"]; + + } elseif(!$profile AND ($data["header"]["author-link"] != "") AND !in_array($network, array("", NETWORK_FEED))) $profile = $data["header"]["author-link"]; } } From a88d950c33ce27f5fe7e5c1183e2daa4a21c6453 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Sun, 3 Apr 2016 20:55:39 +0200 Subject: [PATCH 261/273] set language in HTML header to selected UI language --- view/default.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/default.php b/view/default.php index 121d5212c6..df9adbc392 100644 --- a/view/default.php +++ b/view/default.php @@ -1,5 +1,5 @@ <!DOCTYPE html > -<html itemscope itemtype="http://schema.org/Blog" /> +<html itemscope itemtype="http://schema.org/Blog" lang="<?php echo $lang; ?>"> <head> <title><?php if(x($page,'title')) echo $page['title'] ?></title> <script>var baseurl="<?php echo $a->get_baseurl() ?>";</script> From d9b6355bb8d67bd22bf9dbf3cfb6036b8d8f5af9 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Sun, 3 Apr 2016 21:00:18 +0200 Subject: [PATCH 262/273] added lang info to frost themes --- view/theme/frost-mobile/default.php | 2 +- view/theme/frost/default.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/view/theme/frost-mobile/default.php b/view/theme/frost-mobile/default.php index 3a424ad5f4..332291ca92 100644 --- a/view/theme/frost-mobile/default.php +++ b/view/theme/frost-mobile/default.php @@ -1,5 +1,5 @@ <!DOCTYPE html > -<html> +<html lang="<?php echo $lang; ?>"> <head> <title><?php if(x($page,'title')) echo $page['title'] ?></title> <script>var baseurl="<?php echo $a->get_baseurl() ?>";</script> diff --git a/view/theme/frost/default.php b/view/theme/frost/default.php index c379955f79..c67bdcf20e 100644 --- a/view/theme/frost/default.php +++ b/view/theme/frost/default.php @@ -1,5 +1,5 @@ <!DOCTYPE html > -<html> +<html lang="<?php echo $lang; ?>"> <head> <title><?php if(x($page,'title')) echo $page['title'] ?></title> <script>var baseurl="<?php echo $a->get_baseurl() ?>";</script> From 1ee405c9c3d9e6ae805df34a2b57b80d0428e8cb Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Mon, 4 Apr 2016 08:10:27 +0200 Subject: [PATCH 263/273] added aria describedby elements refering to field help to the input templates --- view/templates/field_checkbox.tpl | 4 ++-- view/templates/field_combobox.tpl | 4 ++-- view/templates/field_input.tpl | 4 ++-- view/templates/field_intcheckbox.tpl | 4 ++-- view/templates/field_openid.tpl | 4 ++-- view/templates/field_password.tpl | 4 ++-- view/templates/field_radio.tpl | 4 ++-- view/templates/field_richtext.tpl | 4 ++-- view/templates/field_select.tpl | 4 ++-- view/templates/field_select_raw.tpl | 4 ++-- view/templates/field_textarea.tpl | 4 ++-- view/templates/field_themeselect.tpl | 4 ++-- view/templates/field_yesno.tpl | 4 ++-- view/templates/login.tpl | 3 ++- 14 files changed, 28 insertions(+), 27 deletions(-) diff --git a/view/templates/field_checkbox.tpl b/view/templates/field_checkbox.tpl index e476c07d72..06796376b6 100644 --- a/view/templates/field_checkbox.tpl +++ b/view/templates/field_checkbox.tpl @@ -1,5 +1,5 @@ <div class='field checkbox' id='div_id_{{$field.0}}'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <input type="checkbox" name='{{$field.0}}' id='id_{{$field.0}}' value="1" {{if $field.2}}checked="checked"{{/if}}> - <span class='field_help'>{{$field.3}}</span> + <input type="checkbox" name='{{$field.0}}' id='id_{{$field.0}}' aria-describedby='{{$field.0}}_tip' value="1" {{if $field.2}}checked="checked"{{/if}}> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_combobox.tpl b/view/templates/field_combobox.tpl index a2f7c3f27e..4586550166 100644 --- a/view/templates/field_combobox.tpl +++ b/view/templates/field_combobox.tpl @@ -7,12 +7,12 @@ {{foreach $field.4 as $opt=>$val}}<option value="{{$val|escape:'html'}}">{{/foreach}} </datalist> *}} - <input id="id_{{$field.0}}" type="text" value="{{$field.2}}"> + <input id="id_{{$field.0}}" type="text" value="{{$field.2}}" aria-describedby='{{$field.0}}_tip'> <select id="select_{{$field.0}}" onChange="$('#id_{{$field.0}}').val($(this).val())"> <option value="">{{$field.5}}</option> {{foreach $field.4 as $opt=>$val}}<option value="{{$val|escape:'html'}}">{{$val}}</option>{{/foreach}} </select> - <span class='field_help'>{{$field.3}}</span> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_input.tpl b/view/templates/field_input.tpl index 6a3328c5cc..8db8e545f3 100644 --- a/view/templates/field_input.tpl +++ b/view/templates/field_input.tpl @@ -1,6 +1,6 @@ <div class='field input' id='wrapper_{{$field.0}}'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <input{{if $field.6 eq 'email'}} type='email'{{elseif $field.6 eq 'url'}} type='url'{{/if}} name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}"{{if $field.4 eq 'required'}} required{{/if}}{{if $field.5 eq 'autofocus'}} autofocus{{/if}}> - <span class='field_help'>{{$field.3}}</span> + <input{{if $field.6 eq 'email'}} type='email'{{elseif $field.6 eq 'url'}} type='url'{{/if}} name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}"{{if $field.4 eq 'required'}} required{{/if}}{{if $field.5 eq 'autofocus'}} autofocus{{/if}} aria-describedby='{{$field.0}}_tip'> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_intcheckbox.tpl b/view/templates/field_intcheckbox.tpl index 2f3c27d920..73bdf60417 100644 --- a/view/templates/field_intcheckbox.tpl +++ b/view/templates/field_intcheckbox.tpl @@ -2,6 +2,6 @@ <div class='field checkbox'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <input type="checkbox" name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.3|escape:'html'}}" {{if $field.2}}checked="true"{{/if}}> - <span class='field_help'>{{$field.4}}</span> + <input type="checkbox" name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.3|escape:'html'}}" {{if $field.2}}checked="true"{{/if}} aria-describedby='{{$field.0}}_tip'> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.4}}</span> </div> diff --git a/view/templates/field_openid.tpl b/view/templates/field_openid.tpl index e5f236c679..25773189f8 100644 --- a/view/templates/field_openid.tpl +++ b/view/templates/field_openid.tpl @@ -1,6 +1,6 @@ <div class='field input openid' id='wrapper_{{$field.0}}'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <input name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}"> - <span class='field_help'>{{$field.3}}</span> + <input name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}" aria-describedby='{{$field.0}}_tip'> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_password.tpl b/view/templates/field_password.tpl index 8a9f0dc330..333ce67c38 100644 --- a/view/templates/field_password.tpl +++ b/view/templates/field_password.tpl @@ -1,6 +1,6 @@ <div class='field password' id='wrapper_{{$field.0}}'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <input type='password' name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}"{{if $field.4 eq 'required'}} required{{/if}}{{if $field.5 eq 'autofocus'}} autofocus{{/if}}> - <span class='field_help'>{{$field.3}}</span> + <input type='password' name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}"{{if $field.4 eq 'required'}} required{{/if}}{{if $field.5 eq 'autofocus'}} autofocus{{/if}} aria-describedby='{{$field.0}}_tip'> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_radio.tpl b/view/templates/field_radio.tpl index 86cc8fc47e..ef8ec4f9a6 100644 --- a/view/templates/field_radio.tpl +++ b/view/templates/field_radio.tpl @@ -2,6 +2,6 @@ <div class='field radio'> <label for='id_{{$field.0}}_{{$field.2}}'>{{$field.1}}</label> - <input type="radio" name='{{$field.0}}' id='id_{{$field.0}}_{{$field.2}}' value="{{$field.2|escape:'html'}}" {{if $field.4}}checked="true"{{/if}}> - <span class='field_help'>{{$field.3}}</span> + <input type="radio" name='{{$field.0}}' id='id_{{$field.0}}_{{$field.2}}' value="{{$field.2|escape:'html'}}" {{if $field.4}}checked="true"{{/if}} aria-describedby={{$field.0}}_tip'> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_richtext.tpl b/view/templates/field_richtext.tpl index f0ea50b58b..67553bb95a 100644 --- a/view/templates/field_richtext.tpl +++ b/view/templates/field_richtext.tpl @@ -2,6 +2,6 @@ <div class='field richtext'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <textarea name='{{$field.0}}' id='id_{{$field.0}}' class="fieldRichtext">{{$field.2}}</textarea> - <span class='field_help'>{{$field.3}}</span> + <textarea name='{{$field.0}}' id='id_{{$field.0}}' class="fieldRichtext" aria-describedby='{{$field.0}}_tip'>{{$field.2}}</textarea> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_select.tpl b/view/templates/field_select.tpl index 4fbbd4beb0..2d037439df 100644 --- a/view/templates/field_select.tpl +++ b/view/templates/field_select.tpl @@ -2,8 +2,8 @@ <div class='field select'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <select name='{{$field.0}}' id='id_{{$field.0}}'> + <select name='{{$field.0}}' id='id_{{$field.0}}' aria-describedby='{{$field.0}}_tip'> {{foreach $field.4 as $opt=>$val}}<option value="{{$opt|escape:'html'}}" {{if $opt==$field.2}}selected="selected"{{/if}}>{{$val}}</option>{{/foreach}} </select> - <span class='field_help'>{{$field.3}}</span> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_select_raw.tpl b/view/templates/field_select_raw.tpl index 02308e2f4b..4c826a0427 100644 --- a/view/templates/field_select_raw.tpl +++ b/view/templates/field_select_raw.tpl @@ -2,8 +2,8 @@ <div class='field select'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <select name='{{$field.0}}' id='id_{{$field.0}}'> + <select name='{{$field.0}}' id='id_{{$field.0}}' aria-describedby='{{$field.0}}_tip'> {{$field.4}} </select> - <span class='field_help'>{{$field.3}}</span> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_textarea.tpl b/view/templates/field_textarea.tpl index 29d3b7a7c6..c37537d6ec 100644 --- a/view/templates/field_textarea.tpl +++ b/view/templates/field_textarea.tpl @@ -2,6 +2,6 @@ <div class='field textarea'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <textarea name='{{$field.0}}' id='id_{{$field.0}}'>{{$field.2}}</textarea> - <span class='field_help'>{{$field.3}}</span> + <textarea name='{{$field.0}}' id='id_{{$field.0}}' aria-describedby='{{$field.0}}_tip'>{{$field.2}}</textarea> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/field_themeselect.tpl b/view/templates/field_themeselect.tpl index edd25dbe0f..51f6057ae3 100644 --- a/view/templates/field_themeselect.tpl +++ b/view/templates/field_themeselect.tpl @@ -2,9 +2,9 @@ {{if $field.5}}<script>$(function(){ previewTheme($("#id_{{$field.0}}")[0]); });</script>{{/if}} <div class='field select'> <label for='id_{{$field.0}}'>{{$field.1}}</label> - <select name='{{$field.0}}' id='id_{{$field.0}}' {{if $field.5}}onchange="previewTheme(this);"{{/if}} > + <select name='{{$field.0}}' id='id_{{$field.0}}' {{if $field.5}}onchange="previewTheme(this);"{{/if}} aria-describedby='{{$field.0}}_tip'> {{foreach $field.4 as $opt=>$val}}<option value="{{$opt|escape:'html'}}" {{if $opt==$field.2}}selected="selected"{{/if}}>{{$val}}</option>{{/foreach}} </select> - <span class='field_help'>{{$field.3}}</span> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> {{if $field.5}}<div id="theme-preview"></div>{{/if}} </div> diff --git a/view/templates/field_yesno.tpl b/view/templates/field_yesno.tpl index de70c5ae6d..155d0488b3 100644 --- a/view/templates/field_yesno.tpl +++ b/view/templates/field_yesno.tpl @@ -2,7 +2,7 @@ <div class='field yesno'> <label for='id_{{$field.0}}'>{{$field.1}}</label> <div class='onoff' id="id_{{$field.0}}_onoff"> - <input type="hidden" name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}"> + <input type="hidden" name='{{$field.0}}' id='id_{{$field.0}}' value="{{$field.2|escape:'html'}}" aria-describedby='{{$field.0}}_tip'> <a href="#" class='off'> {{if $field.4}}{{$field.4.0}}{{else}}OFF{{/if}} </a> @@ -10,5 +10,5 @@ {{if $field.4}}{{$field.4.1}}{{else}}ON{{/if}} </a> </div> - <span class='field_help'>{{$field.3}}</span> + <span class='field_help' role='tooltip' id='{{$field.0}}_tip'>{{$field.3}}</span> </div> diff --git a/view/templates/login.tpl b/view/templates/login.tpl index 37d105c087..caa2e74a4b 100644 --- a/view/templates/login.tpl +++ b/view/templates/login.tpl @@ -1,6 +1,7 @@ <form action="{{$dest_url}}" method="post" > +<fieldset> <input type="hidden" name="auth-params" value="login" /> <div id="login_standard"> @@ -29,7 +30,7 @@ <input type="hidden" name="{{$k}}" value="{{$v|escape:'html'}}" /> {{/foreach}} - +</fieldset> </form> From 952f8514a5f410755a8df1b83655e0d9b6b1cf87 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 5 Apr 2016 23:28:33 +0200 Subject: [PATCH 264/273] "remember me" in session does work now --- include/auth.php | 39 ++++++++------------------------------- include/session.php | 1 - 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/include/auth.php b/include/auth.php index a5b6432fff..4abff19710 100644 --- a/include/auth.php +++ b/include/auth.php @@ -5,35 +5,14 @@ require_once('include/security.php'); require_once('include/datetime.php'); function nuke_session() { - if (get_config('system', 'disable_database_session')) { - session_unset(); - return; - } new_cookie(0); // make sure cookie is deleted on browser close, as a security measure - - unset($_SESSION['authenticated']); - unset($_SESSION['uid']); - unset($_SESSION['visitor_id']); - unset($_SESSION['administrator']); - unset($_SESSION['cid']); - unset($_SESSION['theme']); - unset($_SESSION['mobile-theme']); - unset($_SESSION['page_flags']); - unset($_SESSION['submanage']); - unset($_SESSION['my_url']); - unset($_SESSION['my_address']); - unset($_SESSION['addr']); - unset($_SESSION['return_url']); - + session_unset(); } // login/logout - - - if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-params'))) || ($_POST['auth-params'] !== 'login'))) { if(((x($_POST,'auth-params')) && ($_POST['auth-params'] === 'logout')) || ($a->module === 'logout')) { @@ -41,6 +20,7 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p // process logout request call_hooks("logging_out"); nuke_session(); + new_cookie(-1); info( t('Logged out.') . EOL); goaway(z_root()); } @@ -90,8 +70,7 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p } authenticate_success($r[0], false, false, $login_refresh); } -} -else { +} else { if(isset($_SESSION)) { nuke_session(); @@ -209,13 +188,11 @@ else { } function new_cookie($time) { - if (!get_config('system', 'disable_database_session')) - $old_sid = session_id(); - session_set_cookie_params($time); + if ($time != 0) + $time = $time + time(); - if (!get_config('system', 'disable_database_session')) { - session_regenerate_id(false); - q("UPDATE session SET sid = '%s' WHERE sid = '%s'", dbesc(session_id()), dbesc($old_sid)); - } + $params = session_get_cookie_params(); + setcookie(session_name(), session_id(), $time, $params['path'], $params['domain'], $params['secure'], isset($params['httponly'])); + return; } diff --git a/include/session.php b/include/session.php index 12551efc42..8f9d64606c 100644 --- a/include/session.php +++ b/include/session.php @@ -69,7 +69,6 @@ function ref_session_destroy ($id) { if(! function_exists('ref_session_gc')) { function ref_session_gc($expire) { q("DELETE FROM `session` WHERE `expire` < %d", dbesc(time())); - //q("OPTIMIZE TABLE `sess_data`"); return true; }} From 5df20fda119b8f178fd41002074fbb7bef87df7d Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Wed, 6 Apr 2016 20:13:34 +0200 Subject: [PATCH 265/273] Issue 2241: added language selector to the admin wizard --- mod/install.php | 5 +++++ view/templates/htconfig.tpl | 4 ++++ view/templates/install_settings.tpl | 1 + 3 files changed, 10 insertions(+) diff --git a/mod/install.php b/mod/install.php index 8434b38e38..7013070764 100755 --- a/mod/install.php +++ b/mod/install.php @@ -77,6 +77,7 @@ function install_post(&$a) { $dbdata = notags(trim($_POST['dbdata'])); $phpath = notags(trim($_POST['phpath'])); $timezone = notags(trim($_POST['timezone'])); + $language = notags(trim($_POST['language'])); $adminmail = notags(trim($_POST['adminmail'])); // connect to db @@ -89,6 +90,7 @@ function install_post(&$a) { '$dbpass' => $dbpass, '$dbdata' => $dbdata, '$timezone' => $timezone, + '$language' => $language, '$urlpath' => $urlpath, '$phpath' => $phpath, '$adminmail' => $adminmail @@ -273,6 +275,8 @@ function install_content(&$a) { $adminmail = notags(trim($_POST['adminmail'])); $timezone = ((x($_POST,'timezone')) ? ($_POST['timezone']) : 'America/Los_Angeles'); + /* Installed langs */ + $lang_choices = get_avaiable_languages(); $tpl = get_markup_template('install_settings.tpl'); $o .= replace_macros($tpl, array( @@ -292,6 +296,7 @@ function install_content(&$a) { '$timezone' => field_timezone('timezone', t('Please select a default timezone for your website'), $timezone, ''), + '$language' => array('language', t('System Language:'), $language, t('Set the default language for your Friendica installation interface and to send emails.'), $lang_choices), '$baseurl' => $a->get_baseurl(), diff --git a/view/templates/htconfig.tpl b/view/templates/htconfig.tpl index 6b3bda6173..971bb50482 100644 --- a/view/templates/htconfig.tpl +++ b/view/templates/htconfig.tpl @@ -20,6 +20,10 @@ $a->path = '{{$urlpath}}'; $default_timezone = '{{$timezone}}'; +// Default system language + +$a->config['system']['language'] = '{{$language}}'; + // What is your site name? $a->config['sitename'] = "My Friend Network"; diff --git a/view/templates/install_settings.tpl b/view/templates/install_settings.tpl index 8d6823f114..5584e14365 100644 --- a/view/templates/install_settings.tpl +++ b/view/templates/install_settings.tpl @@ -19,6 +19,7 @@ {{include file="field_input.tpl" field=$adminmail}} {{$timezone}} +{{include file="field_select.tpl" field=$language}} <input id="install-submit" type="submit" name="submit" value="{{$submit|escape:'html'}}" /> From a28372c03232ac2d5b9f2f37e39ec865143ca785 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Wed, 6 Apr 2016 20:18:33 +0200 Subject: [PATCH 266/273] intendation... --- mod/install.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mod/install.php b/mod/install.php index 7013070764..4ad851564c 100755 --- a/mod/install.php +++ b/mod/install.php @@ -77,7 +77,7 @@ function install_post(&$a) { $dbdata = notags(trim($_POST['dbdata'])); $phpath = notags(trim($_POST['phpath'])); $timezone = notags(trim($_POST['timezone'])); - $language = notags(trim($_POST['language'])); + $language = notags(trim($_POST['language'])); $adminmail = notags(trim($_POST['adminmail'])); // connect to db @@ -90,7 +90,7 @@ function install_post(&$a) { '$dbpass' => $dbpass, '$dbdata' => $dbdata, '$timezone' => $timezone, - '$language' => $language, + '$language' => $language, '$urlpath' => $urlpath, '$phpath' => $phpath, '$adminmail' => $adminmail @@ -275,8 +275,8 @@ function install_content(&$a) { $adminmail = notags(trim($_POST['adminmail'])); $timezone = ((x($_POST,'timezone')) ? ($_POST['timezone']) : 'America/Los_Angeles'); - /* Installed langs */ - $lang_choices = get_avaiable_languages(); + /* Installed langs */ + $lang_choices = get_avaiable_languages(); $tpl = get_markup_template('install_settings.tpl'); $o .= replace_macros($tpl, array( @@ -295,8 +295,7 @@ function install_content(&$a) { '$timezone' => field_timezone('timezone', t('Please select a default timezone for your website'), $timezone, ''), - - '$language' => array('language', t('System Language:'), $language, t('Set the default language for your Friendica installation interface and to send emails.'), $lang_choices), + '$language' => array('language', t('System Language:'), $language, t('Set the default language for your Friendica installation interface and to send emails.'), $lang_choices), '$baseurl' => $a->get_baseurl(), From ac9d3922640d3193e42837e3f2d3e5bfd4e87a23 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff <tobias.diekershoff@gmx.net> Date: Wed, 6 Apr 2016 20:21:41 +0200 Subject: [PATCH 267/273] make en the default language --- mod/install.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/install.php b/mod/install.php index 4ad851564c..b1c2010eef 100755 --- a/mod/install.php +++ b/mod/install.php @@ -295,7 +295,7 @@ function install_content(&$a) { '$timezone' => field_timezone('timezone', t('Please select a default timezone for your website'), $timezone, ''), - '$language' => array('language', t('System Language:'), $language, t('Set the default language for your Friendica installation interface and to send emails.'), $lang_choices), + '$language' => array('language', t('System Language:'), 'en', t('Set the default language for your Friendica installation interface and to send emails.'), $lang_choices), '$baseurl' => $a->get_baseurl(), From a3c79d2007884b2057db6279be011e2db6b6cdbe Mon Sep 17 00:00:00 2001 From: rabuzarus <rabuzarus@t-online.de> Date: Thu, 7 Apr 2016 14:43:56 +0200 Subject: [PATCH 268/273] Update ForumManager.php fix some doxygen description --- include/ForumManager.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/ForumManager.php b/include/ForumManager.php index 49417d1831..fe12b45525 100644 --- a/include/ForumManager.php +++ b/include/ForumManager.php @@ -1,12 +1,12 @@ <?php /** - * @file include/forum.php - * @brief Functions related to forum functionality * + * @file include/ForumManager.php + * @brief ForumManager class with it's methods related to forum functionality * */ /** - * @brief This class handles functions related to the forum functionality + * @brief This class handles metheods related to the forum functionality */ class ForumManager { @@ -187,4 +187,4 @@ class ForumManager { return $r; } -} \ No newline at end of file +} From d5bf386cf74fdb928ffe022344d2544363a04bf3 Mon Sep 17 00:00:00 2001 From: rabuzarus <rabuzarus@t-online.de> Date: Thu, 7 Apr 2016 16:58:26 +0200 Subject: [PATCH 269/273] Update ForumManager.php - small correction --- include/ForumManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ForumManager.php b/include/ForumManager.php index fe12b45525..7b5fb1c2f6 100644 --- a/include/ForumManager.php +++ b/include/ForumManager.php @@ -2,7 +2,7 @@ /** * @file include/ForumManager.php - * @brief ForumManager class with it's methods related to forum functionality * + * @brief ForumManager class with its methods related to forum functionality * */ /** From 34dc23b87115b34f1322a8f9c59dddc719bff121 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sat, 9 Apr 2016 22:44:32 +0200 Subject: [PATCH 270/273] API: Improvement for Twidere/Conversation call improved --- include/api.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/include/api.php b/include/api.php index a494e3cdd9..8d74a4612d 100644 --- a/include/api.php +++ b/include/api.php @@ -1524,15 +1524,21 @@ if ($max_id > 0) $sql_extra = ' AND `item`.`id` <= '.intval($max_id); + // Not sure why this query was so complicated. We should keep it here for a while, + // just to make sure that we really don't need it. + // FROM `item` INNER JOIN (SELECT `uri`,`parent` FROM `item` WHERE `id` = %d) AS `temp1` + // ON (`item`.`thr-parent` = `temp1`.`uri` AND `item`.`parent` = `temp1`.`parent`) + $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`, `contact`.`uid` AS `contact-uid` - FROM `item` INNER JOIN (SELECT `uri`,`parent` FROM `item` WHERE `id` = %d) AS `temp1` - ON (`item`.`thr-parent` = `temp1`.`uri` AND `item`.`parent` = `temp1`.`parent`), `contact` - WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 - AND `item`.`uid` = %d AND `item`.`verb` = '%s' AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 + FROM `item` + INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + WHERE `item`.`parent` = %d AND `item`.`visible` + AND NOT `item`.`moderated` AND NOT `item`.`deleted` + AND `item`.`uid` = %d AND `item`.`verb` = '%s' + AND NOT `contact`.`blocked` AND NOT `contact`.`pending` AND `item`.`id`>%d $sql_extra ORDER BY `item`.`id` DESC LIMIT %d ,%d", intval($id), intval(api_user()), @@ -2059,6 +2065,10 @@ $statushtml = trim(bbcode($body, false, false)); + $search = array("<br>", "<blockquote>", "</blockquote>"); + $replace = array("<br>\n", "\n<blockquote>", "</blockquote>\n"); + $statushtml = str_replace($search, $replace, $statushtml); + if ($item['title'] != "") $statushtml = "<h4>".bbcode($item['title'])."</h4>\n".$statushtml; From 90f5cb23f211c951b49914a0d4d0a7c1090d16f6 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Sun, 10 Apr 2016 13:36:26 +0200 Subject: [PATCH 271/273] fbrowser: Take the picture as that mostly fits --- mod/fbrowser.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mod/fbrowser.php b/mod/fbrowser.php index 5836efbe52..110ca9585c 100644 --- a/mod/fbrowser.php +++ b/mod/fbrowser.php @@ -74,9 +74,9 @@ function fbrowser_content($a){ $filename_e = $rr['filename']; } - // Take the second largest picture as preview - $p = q("SELECT `scale` FROM `photo` WHERE `resource-id` = '%s' AND `scale` > %d ORDER BY `resource-id`, `scale` LIMIT 1", - dbesc($rr['resource-id']), intval($rr['hiq'])); + // Take the largest picture that is smaller or equal 640 pixels + $p = q("SELECT `scale` FROM `photo` WHERE `resource-id` = '%s' AND `height` <= 640 AND `width` <= 640 ORDER BY `resource-id`, `scale` LIMIT 1", + dbesc($rr['resource-id'])); if ($p) $scale = $p[0]["scale"]; else From 018b3ed25f8d8c1f0c9e54261777070a4ff23198 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Tue, 12 Apr 2016 22:53:54 +0200 Subject: [PATCH 272/273] API: Just some more elements where Twidere has problems ... --- include/api.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/api.php b/include/api.php index 8d74a4612d..305a86ca13 100644 --- a/include/api.php +++ b/include/api.php @@ -2065,8 +2065,14 @@ $statushtml = trim(bbcode($body, false, false)); - $search = array("<br>", "<blockquote>", "</blockquote>"); - $replace = array("<br>\n", "\n<blockquote>", "</blockquote>\n"); + $search = array("<br>", "<blockquote>", "</blockquote>", + "<h1>", "</h1>", "<h2>", "</h2>", + "<h3>", "</h3>", "<h4>", "</h4>", + "<h5>", "</h5>", "<h6>", "</h6>"); + $replace = array("<br>\n", "\n<blockquote>", "</blockquote>\n", + "\n<h1>", "</h1>\n", "\n<h2>", "</h2>\n", + "\n<h3>", "</h3>\n", "\n<h4>", "</h4>\n", + "\n<h5>", "</h5>\n", "\n<h6>", "</h6>\n"); $statushtml = str_replace($search, $replace, $statushtml); if ($item['title'] != "") From 71caebdae09f74fd6f702887aba712e220820ef7 Mon Sep 17 00:00:00 2001 From: Michael Vogel <icarus@dabo.de> Date: Wed, 13 Apr 2016 07:00:36 +0200 Subject: [PATCH 273/273] Bugfix: Sometimes mentions vanished - now they shouldn't do this anymore --- mod/item.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mod/item.php b/mod/item.php index 14c8203c98..ffb486a7db 100644 --- a/mod/item.php +++ b/mod/item.php @@ -1065,10 +1065,11 @@ function item_content(&$a) { * the appropiate link. * * @param unknown_type $body the text to replace the tag in - * @param unknown_type $inform a comma-seperated string containing everybody to inform - * @param unknown_type $str_tags string to add the tag to - * @param unknown_type $profile_uid - * @param unknown_type $tag the tag to replace + * @param string $inform a comma-seperated string containing everybody to inform + * @param string $str_tags string to add the tag to + * @param integer $profile_uid + * @param string $tag the tag to replace + * @param string $network The network of the post * * @return boolean true if replaced, false if not replaced */ @@ -1176,7 +1177,7 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo //select someone from this user's contacts by name in the current network if (!$r AND ($network != "")) $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network` FROM `contact` WHERE `name` = '%s' AND `network` = '%s' AND `uid` = %d LIMIT 1", - dbesc($newname), + dbesc($name), dbesc($network), intval($profile_uid) ); @@ -1193,7 +1194,7 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo //select someone from this user's contacts by name if(!$r) $r = q("SELECT `id`, `url`, `nick`, `name`, `alias`, `network` FROM `contact` WHERE `name` = '%s' AND `uid` = %d LIMIT 1", - dbesc($newname), + dbesc($name), intval($profile_uid) ); } @@ -1216,13 +1217,13 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo } //if there is an url for this persons profile - if(isset($profile)) { + if (isset($profile) AND ($newname != "")) { $replaced = true; //create profile link $profile = str_replace(',','%2c',$profile); - $newtag = '@[url=' . $profile . ']' . $newname . '[/url]'; - $body = str_replace('@' . $name, $newtag, $body); + $newtag = '@[url='.$profile.']'.$newname.'[/url]'; + $body = str_replace('@'.$name, $newtag, $body); //append tag to str_tags if(! stristr($str_tags,$newtag)) { if(strlen($str_tags)) @@ -1234,7 +1235,7 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo // subscribed to you. But the nickname URL is OK if they are. Grrr. We'll tag both. if(strlen($alias)) { - $newtag = '@[url=' . $alias . ']' . $newname . '[/url]'; + $newtag = '@[url='.$alias.']'.$newname.'[/url]'; if(! stristr($str_tags,$newtag)) { if(strlen($str_tags)) $str_tags .= ',';